add feat/test runner

This commit is contained in:
Samuele Zanca 2021-10-30 04:57:34 -04:00 committed by GitHub
parent 8e568c95c4
commit d5ee42459e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 9109 additions and 96 deletions

View File

@ -6,12 +6,12 @@ module.exports = {
"webextensions": true
},
'extends': 'eslint:recommended',
'ignorePatterns': ['webpack.*', 'dist/'],
'ignorePatterns': ['webpack.*', 'dist/', 'babel.*'],
'parserOptions': {
'ecmaVersion': 12,
'sourceType': 'module'
},
'plugins': ['unused-imports'],
'plugins': ['unused-imports', 'jest'],
'rules': {
'indent': ['error', 2, { 'SwitchCase': 1 }],
'linebreak-style': [
@ -32,5 +32,20 @@ module.exports = {
'warn',
{ 'vars': 'all', 'varsIgnorePattern': '^_', 'args': 'after-used', 'argsIgnorePattern': '^_' }
]
}
},
// This lints typescript files differently than js files.
'overrides': [{
'files': ["**/*.ts"],
'extends': [
'eslint:recommended', "plugin:@typescript-eslint/recommended"
],
'parser': '@typescript-eslint/parser',
'plugins': ['@typescript-eslint', 'unused-imports', 'jest'],
'rules': {
'@typescript-eslint/ban-ts-comment': 'off',
'@typescript-eslint/no-unused-vars': 'off',
// Good in theory, but seems to disables implict function return types
'@typescript-eslint/explicit-module-boundary-types': 'off',
},
}]
}

View File

@ -13,10 +13,14 @@ jobs:
uses: actions/checkout@v2.3.1
with:
persist-credentials: false
- name: Install and Build
run: |
- uses: actions/setup-node@v2
with:
node-version: '12' # TODO IDK what version nighttab is built with normally.
cache: 'npm'
- run: |
npm install
npm run lint
npm test
npm run build
- name: Deploy

View File

@ -0,0 +1,19 @@
name: Run tests
on: [push, pull_request]
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: Checkout
- uses: actions/checkout@v2
- name: Install Dependecies & Test
- uses: actions/setup-node@v2
with:
node-version: '12' # TODO IDK what version nighttab is built with normally.
cache: 'npm'
- run: |
npm install
npm run lint
npm test

6
babel.config.js Normal file
View File

@ -0,0 +1,6 @@
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' } }],
'@babel/preset-typescript',
],
};

8926
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -6,8 +6,9 @@
"scripts": {
"start": "webpack serve --open --config webpack.dev.js",
"build": "webpack --config webpack.prod.js",
"lint": "eslint --fix --ext .js .",
"prepare": "husky install"
"lint": "eslint --fix --ext .js,.ts,.test.ts .",
"prepare": "husky install",
"test": "jest tests/*"
},
"keywords": [
"startpage",
@ -28,18 +29,27 @@
},
"homepage": "https://github.com/zombieFox/nightTab#readme",
"devDependencies": {
"@babel/preset-env": "^7.15.6",
"@babel/preset-typescript": "^7.15.0",
"@types/jest": "^27.0.2",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"copy-webpack-plugin": "^9.0.1",
"css-loader": "^6.3.0",
"css-minimizer-webpack-plugin": "^3.0.2",
"eslint": "^7.32.0",
"eslint-plugin-jest": "^24.5.2",
"eslint-plugin-unused-imports": "^1.1.5",
"html-webpack-plugin": "^5.3.1",
"husky": "^7.0.2",
"jest": "^27.2.4",
"mini-css-extract-plugin": "^2.2.0",
"moment": "^2.29.1",
"sortablejs": "^1.13.0",
"style-loader": "^3.3.0",
"terser-webpack-plugin": "^5.1.4",
"ts-loader": "^9.2.6",
"typescript": "^4.4.3",
"webfontloader": "^1.6.28",
"webpack": "^5.37.1",
"webpack-cli": "^4.7.0",
@ -47,4 +57,4 @@
"webpack-merge": "^5.8.0",
"zip-webpack-plugin": "^4.0.1"
}
}
}

1
tests/index.ts Normal file
View File

@ -0,0 +1 @@
// This file is strictly for utility functions used to bootstrap tests

30
tests/readme.md Normal file
View File

@ -0,0 +1,30 @@
# NightTab Testing Principles
Testing in NightTab consists primarily of unit tests covering functions that manipulate data in some way. Incorrect data transformations/corruption are the most likely thing to break nighttab and thus our focus is on that.
UI testing can get very tedious and so with nighttab we're manually testing visual aspects. Though we're open to discussing adding some regression testing.
All testing must meet the following criteria:
- **Tests Should Be Fast**
- **Tests Should Be Deterministic**
- **Tests Should Be Simple**
- Test Shouldnt Duplicate Implementation Logic
- Tests Should Be Readable
- Tests should be integrated into the development and CI process
- Tests should never be coupled with implementation details
- Mocks should be avoided, when possible.
## Typescript
We're using typescript for testing for two reasons:
1. Benefit of types inside tests
2. New Data Layer will be written in typescript for more robustness.
Linting for typescript is done via file overrides in the `.eslintrc.js` file, meaning js files are linted using their own configuration and then typescript files are linted with a few extra settings on top of those for js.
## Status of Testing
Nighttab started without any automated testing. As the app grows in popularity with over 100k+ users and multiple contributors working on it, we've decided its time to integrate testing. We're slowly adding testing to make the app more robust and easier to improve and refactor.
## File Structure
Tests file should have the `*.test.ts` extension and be placed under `/test/<path>` where path is a direct mirror of its location in `/src/<path>`.
The file `/test/index.ts` is not a test file and is instead meant to be used to keep utility functions used across tests.

49
tests/utility/get.test.ts Normal file
View File

@ -0,0 +1,49 @@
//@ts-nocheck get has no types, for now
import { get } from '../../src/utility/get';
let object;
beforeEach(() => {
object = { theme: { accent: { rgb: { r: 'hello there' } } } };
});
/**
* For some reason get mutates the object its trying to access,
* might be a good idea to remove this "feature".
*/
describe('get.js', () => {
test('should get receive both props', () => {
const path = 'theme.accent.rgb.r';
const result = 'hello there';
expect(get({ object, path })).toBe(result);
});
test('should get receive an incorrect path', () => {
const path = 'theme.accent.rgb.d';
expect(get({ object, path })).toBe('');
expect(object.theme.accent.rgb.d).toStrictEqual(undefined);
});
test('should get receive an incorrect intermediate path', () => {
const path = 'theme.accent.wrong.r';
expect(get({ object, path })).toBe('');
expect(object.theme.accent.wrong).toStrictEqual({});
});
test('should get receive an incorrect intermediate path as a number', () => {
const path = 'theme.accent.1.r';
expect(get({ object, path })).toBe('');
expect(object.theme.accent[1]).toStrictEqual([]);
});
test('should get receive an null prop', () => {
const path = 'theme.accent.rgb.r';
expect(get({ object, path: null })).toBe(false);
expect(get({ object: null, path })).toBe(false);
});
});

View File

@ -0,0 +1,7 @@
import { makePath } from '../../src/utility/makePath';
test('makePath correctly parses the dot notation', () => {
const input = 'theme.accent.rgb.r';
const result = ['theme', 'accent', 'rgb', 'r'];
expect(makePath(input)).toStrictEqual(result);
});

50
tests/utility/set.test.ts Normal file
View File

@ -0,0 +1,50 @@
//@ts-nocheck
import { set } from '../../src/utility/set';
let object;
beforeEach(() => {
object = { theme: { accent: { rgb: { r: 'Hello there!' } } } };
});
/**
* It seems, unlike get, set doesn't mutate objects when it doesn't find keys
*/
describe('set.js', () => {
test('should set update an object as expected', () => {
const path = 'theme.accent.rgb.r';
const result = 'General Kenobi!';
set({ object, path, value: 'General Kenobi!' });
expect(object.theme.accent.rgb.r).toBe(result);
});
test('should get receive an incorrect path', () => {
const path = 'theme.accent.rgb.d';
expect(set({ object, path })).toBe(false);
expect(object.theme.accent.rgb.d).toBe(undefined);
});
test('should get receive an incorrect intermediate path', () => {
const path = 'theme.accent.wrong.r';
expect(set({ object, path })).toBe(false);
expect(object.theme.accent.wrong).toStrictEqual(undefined);
});
test('should get receive an incorrect path including a number', () => {
const path = 'theme.accent.1.r';
expect(set({ object, path })).toBe(false);
expect(object.theme.accent[1]).toBe(undefined);
});
test('should get receive an null prop', () => {
const path = 'theme.accent.rgb.r';
expect(set({ object, path: null })).toBe(false);
expect(set({ object: null, path })).toBe(false);
});
});

28
tsconfig.json Normal file
View File

@ -0,0 +1,28 @@
{
"compilerOptions": {
/* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"target": "es5",
/* Specify a set of bundled library declaration files that describe the target runtime environment. */
"lib": [
"DOM"
],
/* Allow JavaScript files to be a part of your program. Use the `checkJS` option to get errors from these files. */
"allowJs": true,
/* Allow 'import x from y' when a module doesn't have a default export. */
"allowSyntheticDefaultImports": true,
/* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */
"esModuleInterop": true,
/* Ensure that casing is correct in imports. */
"forceConsistentCasingInFileNames": true,
/* Enable importing .json files */
"resolveJsonModule": true,
/* Skip type checking all .d.ts files. */
"skipLibCheck": true,
/* Enable all strict type-checking options. */
"strict": true,
/* Specify an output folder for all emitted files. */
"outDir": "./dist/",
/* Enable error reporting for expressions and declarations with an implied `any` type.. */
"noImplicitAny": true,
}
}

View File

@ -12,22 +12,34 @@ module.exports = {
clean: true
},
module: {
rules: [{
test: /\.css$/i,
use: ['style-loader', 'css-loader']
}, {
test: /\.(ttf|woff|woff2)$/,
type: 'asset/resource',
generator: {
filename: 'font/[name][ext]',
rules: [
{
test: /\.ts?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
{
test: /\.css$/i,
use: ['style-loader', 'css-loader']
},
{
test: /\.(ttf|woff|woff2)$/,
type: 'asset/resource',
generator: {
filename: 'font/[name][ext]',
}
},
{
test: /\.(jpe?g|png|gif|svg)$/i,
type: 'asset/resource',
generator: {
filename: 'image/[name][ext]',
}
}
}, {
test: /\.(jpe?g|png|gif|svg)$/i,
type: 'asset/resource',
generator: {
filename: 'image/[name][ext]',
}
}]
]
},
resolve: {
extensions: ['.ts', '.js'],
},
plugins: [
new HtmlWebpackPlugin({