mirror of
https://github.com/checktheroads/hyperglass
synced 2024-05-11 05:55:08 +00:00
Add UI tests
This commit is contained in:
31
hyperglass/ui/jest.config.js
Normal file
31
hyperglass/ui/jest.config.js
Normal 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;
|
1
hyperglass/ui/jest.setup.js
Normal file
1
hyperglass/ui/jest.setup.js
Normal file
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom/extend-expect';
|
9
hyperglass/ui/package.json
vendored
9
hyperglass/ui/package.json
vendored
@@ -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"
|
||||
}
|
||||
|
76
hyperglass/ui/util/common.test.ts
Normal file
76
hyperglass/ui/util/common.test.ts
Normal 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');
|
||||
});
|
||||
});
|
@@ -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();
|
||||
}
|
||||
|
14
hyperglass/ui/util/theme.test.ts
Normal file
14
hyperglass/ui/util/theme.test.ts
Normal 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
2160
hyperglass/ui/yarn.lock
vendored
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user