Skip to content

Commit

Permalink
[RSN-53] Add pre-commit hook to run fmt and lint (#44)
Browse files Browse the repository at this point in the history
* chore: configure linters for the project

* ci: add pre-commit hook to run fmt and lint

* ci: unblock ci caused by yarn.lock error

* chore: remove unnecessary added files

* fix: dotnet format now can fail pre-commit
  • Loading branch information
raczu authored Jun 5, 2024
1 parent a3babe3 commit d814d53
Show file tree
Hide file tree
Showing 34 changed files with 2,666 additions and 1,049 deletions.
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

0 comments on commit d814d53

Please sign in to comment.