1
0
mirror of https://github.com/checktheroads/hyperglass synced 2024-05-11 05:55:08 +00:00

Add UI tests

This commit is contained in:
thatmattlove
2021-09-21 09:54:45 -07:00
parent 85566b81ab
commit 082c4175f4
7 changed files with 2268 additions and 90 deletions

View File

@@ -0,0 +1,31 @@
/**
* Jest Testing Configuration
*
* @see https://nextjs.org/docs/testing
* @type {import('@jest/types').Config.InitialOptions}
*/
const jestConfig = {
collectCoverageFrom: ['**/*.{ts,tsx}', '!**/*.d.ts', '!**/node_modules/**'],
testPathIgnorePatterns: ['<rootDir>/node_modules/', '<rootDir>/.next/'],
testEnvironment: 'jsdom',
transform: { '^.+\\.(js|jsx|ts|tsx)$': ['babel-jest', { presets: ['next/babel'] }] },
transformIgnorePatterns: ['/node_modules/', '^.+\\.module\\.(css|sass|scss)$'],
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
moduleNameMapper: {
'^@/components/(.*)$': '<rootDir>/components/$1',
'^~/components': ['components/index'],
'^~/components/(.*)$': '<rootDir>/components/$1',
'^~/context': '<rootDir>/context/index',
'^~/context/(.*)$': '<rootDir>/context/$1',
'^~/hooks': '<rootDir>/hooks/index',
'^~/hooks/(.*)$': '<rootDir>/hooks/$1',
'^~/state': '<rootDir>/state/index',
'^~/state/(.*)$': '<rootDir>/state/$1',
'^~/types': '<rootDir>/types/index',
'^~/types/(.*)$': '<rootDir>/types/$1',
'^~/util': '<rootDir>/util/index',
'^~/util/(.*)$': '<rootDir>/util/$1',
},
};
module.exports = jestConfig;

View File

@@ -0,0 +1 @@
import '@testing-library/jest-dom/extend-expect';

View File

@@ -11,7 +11,8 @@
"start": "next start",
"typecheck": "tsc --noEmit",
"format": "prettier --config ./.prettierrc -c -w .",
"build": "next build && next export -o ../hyperglass/static/ui"
"build": "next build && next export -o ../hyperglass/static/ui",
"test": "jest"
},
"browserslist": "> 0.25%, not dead",
"dependencies": {
@@ -47,6 +48,8 @@
"zustand": "^3.5.10"
},
"devDependencies": {
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^12.1.0",
"@types/dagre": "^0.7.44",
"@types/node": "^14.14.41",
"@types/react": "^17.0.3",
@@ -56,6 +59,7 @@
"@typescript-eslint/eslint-plugin": "^4.31.0",
"@typescript-eslint/parser": "^4.31.0",
"babel-eslint": "^10.1.0",
"babel-jest": "^27.2.1",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-import-resolver-typescript": "^2.4.0",
@@ -67,8 +71,11 @@
"eslint-plugin-react-hooks": "^4.2.0",
"express": "^4.17.1",
"http-proxy-middleware": "0.20.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^27.2.1",
"prettier": "^2.3.2",
"prettier-eslint": "^13.0.0",
"react-test-renderer": "^17.0.2",
"type-fest": "^2.3.2",
"typescript": "^4.4.2"
}

View File

@@ -0,0 +1,76 @@
import { all, chunkArray, entries, dedupObjectArray, andJoin } from './common';
test('all - all items are truthy', () => {
expect(all(1 === 1, true, 'one' === 'one')).toBe(true);
});
test('all - one item is not truthy', () => {
expect(all(1 === 1, false, 'one' === 'one')).toBe(false);
});
describe('chunkArray - chunk array into arrays of n size', () => {
const input = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
const result = chunkArray(input, 2);
test('chunked array length', () => {
expect(result).toHaveLength(Math.round(input.length / 2));
});
test.each(result)('verify chunk %#', item => {
expect(input).toContain(item);
});
});
describe('entries - typed Object.entries()', () => {
const obj = { one: 1, two: 2, three: 3 };
const result = entries(obj);
const expectedKeys = ['one', 'two', 'three'];
const expectedValues = [1, 2, 3];
test.each(result)('verify k/v pair %#', (k, v) => {
expect(expectedKeys).toContain(k);
expect(expectedValues).toContain(v);
});
});
describe('dedupObjectArray - deduplicate object array', () => {
const objArray = [
{ one: 1, two: 2, three: 3 },
{ four: 4, five: 5, six: 6 },
{ seven: 7, eight: 8, nine: 9 },
{ zero: 0, one: 1, thousand: 1_000 },
];
const result = dedupObjectArray(objArray, 'one');
test('ensure duplicate object is removed', () => {
expect(result.length).toBe(3);
});
test.each(result)('verify objects', obj => {
expect(obj).toEqual(expect.not.objectContaining(objArray[3]));
});
});
describe('andJoin - join array of strings to sentence structure', () => {
test('basic sentence', () => {
const result = andJoin(['Tom', 'Dick', 'Harry']);
expect(result).toBe('Tom, Dick, & Harry');
});
test('one item', () => {
const result = andJoin(['Tom']);
expect(result).toBe('Tom');
});
test('mixed types', () => {
// @ts-expect-error Test case
const result = andJoin(['Tom', 100, 'Harry']);
expect(result).toBe('Tom & Harry');
});
test('(options) wrapped', () => {
const result = andJoin(['Tom', 'Dick', 'Harry'], { wrap: '*' });
expect(result).toBe('*Tom*, *Dick*, & *Harry*');
});
test("(options) 'and' separator", () => {
const result = andJoin(['Tom', 'Dick', 'Harry'], { separator: 'and' });
expect(result).toBe('Tom, Dick, and Harry');
});
test('(options) no oxford comma', () => {
const result = andJoin(['Tom', 'Dick', 'Harry'], { oxfordComma: false });
expect(result).toBe('Tom, Dick & Harry');
});
});

View File

@@ -16,63 +16,6 @@ export function chunkArray<A extends unknown>(array: A[], size: number): A[][] {
return result;
}
type PathPart = {
base: number;
children: PathPart[];
};
/**
* Arrange an array of arrays into a tree of nodes.
*
* Blatantly stolen from:
* @see https://gist.github.com/stephanbogner/4b590f992ead470658a5ebf09167b03d
*/
export function arrangeIntoTree<P extends unknown>(paths: P[][]): PathPart[] {
const tree = [] as PathPart[];
for (let i = 0; i < paths.length; i++) {
const path = paths[i];
let currentLevel = tree;
for (let j = 0; j < path.length; j++) {
const part = path[j];
const existingPath = findWhere<PathPart, typeof part>(currentLevel, 'base', part);
if (existingPath !== false) {
currentLevel = existingPath.children;
} else {
const newPart = {
base: part,
children: [],
} as PathPart;
currentLevel.push(newPart);
currentLevel = newPart.children;
}
}
}
return tree;
function findWhere<A extends Record<string, unknown>, V extends unknown>(
array: A[],
idx: string,
value: V,
): A | false {
let t = 0;
while (t < array.length && array[t][idx] !== value) {
t++;
}
if (t < array.length) {
return array[t];
} else {
return false;
}
}
}
/**
* Strictly typed version of `Object.entries()`.
*/
@@ -117,7 +60,13 @@ export function dedupObjectArray<E extends Record<string, unknown>, P extends ke
property: P,
): E[] {
return arr.reduce((acc: E[], current: E) => {
const x = acc.find(item => item[property] === current[property]);
const x = acc.find(item => {
const itemValue = item[property];
const currentValue = current[property];
const validType = all(typeof itemValue !== 'undefined', typeof currentValue !== 'undefined');
return validType && itemValue === currentValue;
});
if (!x) {
return acc.concat([current]);
} else {
@@ -169,7 +118,7 @@ export function andJoin(values: string[], options?: AndJoinOptions): string {
const last = [wrap, lastElement, wrap].join('');
if (parts.length > 0) {
const main = parts.map(p => [wrap, p, wrap].join('')).join(', ');
const comma = oxfordComma && parts.length > 2 ? ',' : '';
const comma = oxfordComma && parts.length >= 2 ? ',' : '';
const result = `${main}${comma} ${separator} ${last}`;
return result.trim();
}

View File

@@ -0,0 +1,14 @@
import { googleFontUrl } from './theme';
describe('google font URL generation', () => {
test('no space font', () => {
const result = googleFontUrl('Inter', [100, 200, 300]);
expect(result).toBe('https://fonts.googleapis.com/css?family=Inter:100,200,300&display=swap');
});
test('space font', () => {
const result = googleFontUrl('Open Sans', [100, 200, 300]);
expect(result).toBe(
'https://fonts.googleapis.com/css?family=Open+Sans:100,200,300&display=swap',
);
});
});

2160
hyperglass/ui/yarn.lock vendored

File diff suppressed because it is too large Load Diff