Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RSN-53] Add pre-commit hook to run fmt and lint #44

Merged
merged 5 commits into from
Jun 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Client/reasn-client/.husky/common.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
command_exists () {
command -v "$1" >/dev/null 2>&1
}

# Workaround for Windows 10, Git Bash, and Yarn
if command_exists winpty && test -t 1; then
exec < /dev/tty
fi
9 changes: 9 additions & 0 deletions Client/reasn-client/.husky/pre-commit
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# .husky/pre-commit
. "$(dirname -- "$0")/common.sh"

(cd ./Client/reasn-client && yarn lint-staged)
(cd ./Server/ReasnAPI && dotnet format)
if ! git diff --quiet; then
echo "🚫 dotnet format made changes, commit aborted."
exit 1
fi
10 changes: 10 additions & 0 deletions Client/reasn-client/apps/native/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module.exports = {
extends: ["expo", "prettier"],
plugins: ["prettier"],
rules: {
"prettier/prettier": "error",
},
globals: {
__dirname: true,
},
};
12 changes: 9 additions & 3 deletions Client/reasn-client/apps/native/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web",
"eject": "expo eject"
"eject": "expo eject",
"lint": "eslint . --ext .js,.jsx,.ts,.tsx"
},
"dependencies": {
"@reasn/ui": "*",
"expo": "^49.0.21",
"expo": "50.0.18",
"expo-status-bar": "~1.7.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
Expand All @@ -21,9 +22,14 @@
},
"devDependencies": {
"@babel/core": "^7.23.7",
"@expo/webpack-config": "^19.0.0",
"@expo/webpack-config": "^19.0.1",
"@types/react": "^18.2.46",
"@types/react-native": "^0.73.0",
"eslint": "^8.57.0",
"eslint-config-expo": "^7.1.2",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"prettier": "^3.2.5",
"typescript": "^5.3.3"
}
}
2 changes: 1 addition & 1 deletion Client/reasn-client/apps/web/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"extends": "next/core-web-vitals"
"extends": ["next/core-web-vitals", "prettier"]
}
2 changes: 1 addition & 1 deletion Client/reasn-client/apps/web/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import '../styles/global.css';
import "../styles/global.css";
import "@reasn/ui/src/styles.css";

export default function RootLayout({
Expand Down
3 changes: 2 additions & 1 deletion Client/reasn-client/apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
"@types/react": "^18.2.46",
"@types/react-dom": "^18.2.18",
"babel-plugin-react-native-web": "^0.19.10",
"eslint": "^8.56.0",
"eslint": "^8.57.0",
"eslint-config-next": "14.0.4",
"eslint-config-prettier": "^9.1.0",
"typescript": "^5.3.3"
}
}
10 changes: 5 additions & 5 deletions Client/reasn-client/apps/web/postcss.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
7 changes: 3 additions & 4 deletions Client/reasn-client/apps/web/tailwind.config.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: [
'./app/**/*.{js,jsx,ts,tsx}',
'../../packages/ui/src/**/*.{js,jsx,ts,tsx}'
"./app/**/*.{js,jsx,ts,tsx}",
"../../packages/ui/src/**/*.{js,jsx,ts,tsx}",
],
theme: {
extend: {},
},
plugins: [],
}

};
35 changes: 12 additions & 23 deletions Client/reasn-client/jest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* https://jestjs.io/docs/configuration
*/

import type { JestConfigWithTsJest } from 'ts-jest'
import type { JestConfigWithTsJest } from "ts-jest";

const config: JestConfigWithTsJest = {
// All imported modules in your tests should be mocked automatically
Expand Down Expand Up @@ -73,9 +73,7 @@ const config: JestConfigWithTsJest = {
// maxWorkers: "50%",

// An array of directory names to be searched recursively up from the requiring module's location
moduleDirectories: [
"node_modules"
],
moduleDirectories: ["node_modules"],

// An array of file extensions your modules use
moduleFileExtensions: [
Expand All @@ -86,14 +84,15 @@ const config: JestConfigWithTsJest = {
"ts",
"tsx",
"json",
"node"
"node",
],

// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
moduleNameMapper: {
"^@reasn/common/services/(.*)$": "<rootDir>/packages/common/services/$1",
"^@reasn/common/enums/(.*)$": "<rootDir>/packages/common/enums/$1",
"^@reasn/common/interfaces/(.*)$": "<rootDir>/packages/common/interfaces/$1",
"^@reasn/common/interfaces/(.*)$":
"<rootDir>/packages/common/interfaces/$1",
"^@reasn/common/errors/(.*)$": "<rootDir>/packages/common/errors/$1",
},

Expand Down Expand Up @@ -131,9 +130,7 @@ const config: JestConfigWithTsJest = {
// rootDir: undefined,

// A list of paths to directories that Jest should use to search for files in
roots: [
"<rootDir>"
],
roots: ["<rootDir>"],

// Allows you to use a custom runner instead of Jest's default test runner
// runner: "jest-runner",
Expand All @@ -142,7 +139,7 @@ const config: JestConfigWithTsJest = {
// setupFiles: [],

// A list of paths to modules that run some code to configure or set up the testing framework before each test
setupFilesAfterEnv: ['jest-fetch-mock'],
setupFilesAfterEnv: ["jest-fetch-mock"],

// The number of seconds after which a test is considered as slow and reported as such in the results.
// slowTestThreshold: 5,
Expand All @@ -160,15 +157,10 @@ const config: JestConfigWithTsJest = {
// testLocationInResults: false,

// The glob patterns Jest uses to detect test files
testMatch: [
"**/__tests__/**/*.[jt]s?(x)",
"**/?(*.)+(spec|test).[tj]s?(x)"
],
testMatch: ["**/__tests__/**/*.[jt]s?(x)", "**/?(*.)+(spec|test).[tj]s?(x)"],

// An array of regexp pattern strings that are matched against all test paths, matched tests are skipped
testPathIgnorePatterns: [
"\\\\node_modules\\\\"
],
testPathIgnorePatterns: ["\\\\node_modules\\\\"],

// The regexp pattern or array of patterns that Jest uses to detect test files
// testRegex: [],
Expand All @@ -181,14 +173,11 @@ const config: JestConfigWithTsJest = {

// A map from regular expressions to paths to transformers
transform: {
"^.+\\.(ts|tsx)?$": "ts-jest"
"^.+\\.(ts|tsx)?$": "ts-jest",
},

// An array of regexp pattern strings that are matched against all source file paths, matched files will skip transformation
transformIgnorePatterns: [
"\\\\node_modules\\\\",
"\\.pnp\\.[^\\\\]+$"
],
transformIgnorePatterns: ["\\\\node_modules\\\\", "\\.pnp\\.[^\\\\]+$"],

// An array of regexp pattern strings that are matched against all modules before the module loader will automatically return a mock for them
// unmockedModulePathPatterns: undefined,
Expand All @@ -203,4 +192,4 @@ const config: JestConfigWithTsJest = {
// watchman: true,
};

export default config;
export default config;
17 changes: 15 additions & 2 deletions Client/reasn-client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@
"scripts": {
"dev": "turbo run dev",
"dev:web": "turbo run dev --filter=web --filter=@reasn/ui --filter=@reasn/typescript-config",
"dev:mobile": "turbo run dev --filter=native --filter=@reasn/ui --filter=@reasn/typescript-config",
"dev:mobile": "turbo run dev --filter=native --filter=@reasn/ui",
"build": "turbo run build",
"clean": "turbo run clean && rm -rf node_modules",
"lint": "turbo run lint",
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\" --ignore-path .gitignore",
"test": "jest"
"test": "jest",
"postinstall": "cd ../../ && husky Client/reasn-client/.husky"
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "^20.12.11",
"husky": "^9.0.11",
"jest": "^29.7.0",
"jest-fetch-mock": "^3.0.3",
"lint-staged": "^15.2.2",
"prettier": "^3.1.1",
"ts-jest": "^29.1.2",
"ts-node": "^10.9.2",
Expand All @@ -34,5 +39,13 @@
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3"
},
"lint-staged": {
"apps/**/*.{js,ts,jsx,tsx}": [
"eslint --fix"
],
"**/*.{ts,tsx,js,jsx,json,md}": [
"prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\" --ignore-path .gitignore"
]
}
}
Original file line number Diff line number Diff line change
@@ -1,80 +1,86 @@
import { sendRequest } from '@reasn/common/services/apiServices';
import { getAuthDataFromSessionStorage } from '@reasn/common/services/authorizationServices';
import { sendRequest } from "@reasn/common/services/apiServices";
import { getAuthDataFromSessionStorage } from "@reasn/common/services/authorizationServices";
import { AuthData } from "@reasn/common/interfaces/AuthData";
import { HttpMethod } from '@reasn/common/enums/serviceEnums';
import { describe, expect, it, jest, beforeEach } from '@jest/globals';
import fetch from 'cross-fetch';
import ApiConnectionError from '@reasn/common/errors/ApiConnectionError';
import ApiAuthorizationError from '@reasn/common/errors/ApiAuthorizationError';
import { HttpMethod } from "@reasn/common/enums/serviceEnums";
import { describe, expect, it, jest, beforeEach } from "@jest/globals";
import fetch from "cross-fetch";
import ApiConnectionError from "@reasn/common/errors/ApiConnectionError";
import ApiAuthorizationError from "@reasn/common/errors/ApiAuthorizationError";

jest.mock('cross-fetch');
jest.mock('@reasn/common/services/authorizationServices');
jest.mock("cross-fetch");
jest.mock("@reasn/common/services/authorizationServices");

describe('sendRequest', () => {
beforeEach(() => {
(fetch as jest.Mock).mockClear();
(getAuthDataFromSessionStorage as jest.Mock).mockClear();
});
describe("sendRequest", () => {
beforeEach(() => {
(fetch as jest.Mock).mockClear();
(getAuthDataFromSessionStorage as jest.Mock).mockClear();
});

it('should return data when response is ok', async () => {
const mockData = { key: 'value' };
it("should return data when response is ok", async () => {
const mockData = { key: "value" };
(fetch as jest.Mock).mockImplementationOnce(() => ({
ok: true,
json: () => Promise.resolve(mockData),
}));
ok: true,
json: () => Promise.resolve(mockData),
}));

const data = await sendRequest('http://example.com', HttpMethod.GET);
const data = await sendRequest("http://example.com", HttpMethod.GET);

expect(data).toEqual(mockData);
});

it('should throw an API error when response is not ok', async () => {
const mockData = { message: 'Error message' };
it("should throw an API error when response is not ok", async () => {
const mockData = { message: "Error message" };
(fetch as jest.Mock).mockImplementationOnce(() => ({
ok: false,
status: 500,
statusText: mockData.message,
json: () => Promise.resolve(mockData),
}));
ok: false,
status: 500,
statusText: mockData.message,
json: () => Promise.resolve(mockData),
}));

await expect(sendRequest('http://example.com', HttpMethod.GET)).rejects.toThrow(ApiConnectionError);
await expect(
sendRequest("http://example.com", HttpMethod.GET),
).rejects.toThrow(ApiConnectionError);
});

it('should include auth token in headers when authRequired is true', async () => {
const mockData = { key: 'value' };
it("should include auth token in headers when authRequired is true", async () => {
const mockData = { key: "value" };
(fetch as jest.Mock).mockImplementationOnce(() => ({
ok: true,
json: () => Promise.resolve(mockData),
}));
(getAuthDataFromSessionStorage as jest.Mock).mockReturnValueOnce({ token: 'token' } as AuthData);
ok: true,
json: () => Promise.resolve(mockData),
}));
(getAuthDataFromSessionStorage as jest.Mock).mockReturnValueOnce({
token: "token",
} as AuthData);

await sendRequest('http://example.com', HttpMethod.GET, {}, true);
await sendRequest("http://example.com", HttpMethod.GET, {}, true);

expect(fetch).toHaveBeenCalledWith('http://example.com', {
method: HttpMethod.GET,
headers: { Authorization: 'Bearer token' },
});
expect(fetch).toHaveBeenCalledWith("http://example.com", {
method: HttpMethod.GET,
headers: { Authorization: "Bearer token" },
});
});

it('should have correct fetch options', async () => {
const mockData = { key: 'value' };
it("should have correct fetch options", async () => {
const mockData = { key: "value" };
(fetch as jest.Mock).mockImplementationOnce(() => ({
ok: true,
json: () => Promise.resolve(mockData),
}));
ok: true,
json: () => Promise.resolve(mockData),
}));

await sendRequest('http://example.com', HttpMethod.POST, { key: 'value' });
await sendRequest("http://example.com", HttpMethod.POST, { key: "value" });

expect(fetch).toHaveBeenCalledWith('http://example.com', {
method: HttpMethod.POST,
headers: {},
body: JSON.stringify({ key: 'value' }),
});
expect(fetch).toHaveBeenCalledWith("http://example.com", {
method: HttpMethod.POST,
headers: {},
body: JSON.stringify({ key: "value" }),
});
});

it('should throw an AUTH error when authRequired is true and no auth data is found', async () => {
it("should throw an AUTH error when authRequired is true and no auth data is found", async () => {
(getAuthDataFromSessionStorage as jest.Mock).mockReturnValueOnce(null);

await expect(sendRequest('http://example.com', HttpMethod.GET, {}, true)).rejects.toThrow(ApiAuthorizationError);
await expect(
sendRequest("http://example.com", HttpMethod.GET, {}, true),
).rejects.toThrow(ApiAuthorizationError);
});
});
});
Loading
Loading