Set up comprehensive frontend testing infrastructure
- Install Jest for unit testing with React Testing Library - Install Playwright for end-to-end testing - Configure Jest with proper TypeScript support and module mapping - Create test setup files and utilities for both unit and e2e tests Components: * Jest configuration with coverage thresholds * Playwright configuration with browser automation * Unit tests for LoginForm, AuthContext, and useSocketIO hook * E2E tests for authentication, dashboard, and agents workflows * GitHub Actions workflow for automated testing * Mock data and API utilities for consistent testing * Test documentation with best practices Testing features: - Unit tests with 70% coverage threshold - E2E tests with API mocking and user journey testing - CI/CD integration for automated test runs - Cross-browser testing support with Playwright - Authentication system testing end-to-end 🚀 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
211
frontend/node_modules/@jest/snapshot-utils/src/__tests__/utils.test.ts
generated
vendored
Normal file
211
frontend/node_modules/@jest/snapshot-utils/src/__tests__/utils.test.ts
generated
vendored
Normal file
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
jest.mock('graceful-fs', () => ({
|
||||
...jest.createMockFromModule<typeof import('fs')>('fs'),
|
||||
existsSync: jest.fn().mockReturnValue(true),
|
||||
}));
|
||||
|
||||
import * as path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import * as fs from 'graceful-fs';
|
||||
import {
|
||||
SNAPSHOT_GUIDE_LINK,
|
||||
SNAPSHOT_VERSION,
|
||||
SNAPSHOT_VERSION_WARNING,
|
||||
getSnapshotData,
|
||||
keyToTestName,
|
||||
saveSnapshotFile,
|
||||
testNameToKey,
|
||||
} from '../utils';
|
||||
|
||||
test('keyToTestName()', () => {
|
||||
expect(keyToTestName('abc cde 12')).toBe('abc cde');
|
||||
expect(keyToTestName('abc cde 12')).toBe('abc cde ');
|
||||
expect(keyToTestName('test with\\r\\nCRLF 1')).toBe('test with\r\nCRLF');
|
||||
expect(keyToTestName('test with\\rCR 1')).toBe('test with\rCR');
|
||||
expect(keyToTestName('test with\\nLF 1')).toBe('test with\nLF');
|
||||
expect(() => keyToTestName('abc cde')).toThrow(
|
||||
'Snapshot keys must end with a number.',
|
||||
);
|
||||
});
|
||||
|
||||
test('testNameToKey', () => {
|
||||
expect(testNameToKey('abc cde', 1)).toBe('abc cde 1');
|
||||
expect(testNameToKey('abc cde ', 12)).toBe('abc cde 12');
|
||||
});
|
||||
|
||||
test('testNameToKey escapes line endings to prevent collisions', () => {
|
||||
expect(testNameToKey('test with\r\nCRLF', 1)).toBe('test with\\r\\nCRLF 1');
|
||||
expect(testNameToKey('test with\rCR', 1)).toBe('test with\\rCR 1');
|
||||
expect(testNameToKey('test with\nLF', 1)).toBe('test with\\nLF 1');
|
||||
|
||||
expect(testNameToKey('test\r\n', 1)).not.toBe(testNameToKey('test\r', 1));
|
||||
expect(testNameToKey('test\r\n', 1)).not.toBe(testNameToKey('test\n', 1));
|
||||
expect(testNameToKey('test\r', 1)).not.toBe(testNameToKey('test\n', 1));
|
||||
});
|
||||
|
||||
test('keyToTestName reverses testNameToKey transformation', () => {
|
||||
const testCases = [
|
||||
'simple test',
|
||||
'test with\r\nCRLF',
|
||||
'test with\rCR only',
|
||||
'test with\nLF only',
|
||||
'mixed\r\nline\rendings\n',
|
||||
'test\r',
|
||||
'test\r\n',
|
||||
'test\n',
|
||||
];
|
||||
|
||||
for (const testName of testCases) {
|
||||
const key = testNameToKey(testName, 1);
|
||||
const recovered = keyToTestName(key);
|
||||
expect(recovered).toBe(testName);
|
||||
}
|
||||
});
|
||||
|
||||
test('saveSnapshotFile() works with \r\n', () => {
|
||||
const filename = path.join(__dirname, 'remove-newlines.snap');
|
||||
const data = {
|
||||
myKey: '<div>\r\n</div>',
|
||||
};
|
||||
|
||||
saveSnapshotFile(data, filename);
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
filename,
|
||||
`// Jest Snapshot v1, ${SNAPSHOT_GUIDE_LINK}\n\n` +
|
||||
'exports[`myKey`] = `<div>\n</div>`;\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('saveSnapshotFile() works with \r', () => {
|
||||
const filename = path.join(__dirname, 'remove-newlines.snap');
|
||||
const data = {
|
||||
myKey: '<div>\r</div>',
|
||||
};
|
||||
|
||||
saveSnapshotFile(data, filename);
|
||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||
filename,
|
||||
`// Jest Snapshot v1, ${SNAPSHOT_GUIDE_LINK}\n\n` +
|
||||
'exports[`myKey`] = `<div>\n</div>`;\n',
|
||||
);
|
||||
});
|
||||
|
||||
test('getSnapshotData() throws when no snapshot version', () => {
|
||||
const filename = path.join(__dirname, 'old-snapshot.snap');
|
||||
jest
|
||||
.mocked(fs.readFileSync)
|
||||
.mockReturnValue('exports[`myKey`] = `<div>\n</div>`;\n');
|
||||
const update = 'none';
|
||||
|
||||
expect(() => getSnapshotData(filename, update)).toThrow(
|
||||
chalk.red(
|
||||
`${chalk.bold('Outdated snapshot')}: No snapshot header found. ` +
|
||||
'Jest 19 introduced versioned snapshots to ensure all developers on ' +
|
||||
'a project are using the same version of Jest. ' +
|
||||
'Please update all snapshots during this upgrade of Jest.\n\n',
|
||||
) + SNAPSHOT_VERSION_WARNING,
|
||||
);
|
||||
});
|
||||
|
||||
test('getSnapshotData() throws for older snapshot version', () => {
|
||||
const filename = path.join(__dirname, 'old-snapshot.snap');
|
||||
jest
|
||||
.mocked(fs.readFileSync)
|
||||
.mockReturnValue(
|
||||
`// Jest Snapshot v0.99, ${SNAPSHOT_GUIDE_LINK}\n\n` +
|
||||
'exports[`myKey`] = `<div>\n</div>`;\n',
|
||||
);
|
||||
const update = 'none';
|
||||
|
||||
expect(() => getSnapshotData(filename, update)).toThrow(
|
||||
`${chalk.red(
|
||||
`${chalk.red.bold('Outdated snapshot')}: The version of the snapshot ` +
|
||||
'file associated with this test is outdated. The snapshot file ' +
|
||||
'version ensures that all developers on a project are using ' +
|
||||
'the same version of Jest. ' +
|
||||
'Please update all snapshots during this upgrade of Jest.',
|
||||
)}\n\nExpected: v${SNAPSHOT_VERSION}\n` +
|
||||
`Received: v0.99\n\n${SNAPSHOT_VERSION_WARNING}`,
|
||||
);
|
||||
});
|
||||
|
||||
test('getSnapshotData() throws for newer snapshot version', () => {
|
||||
const filename = path.join(__dirname, 'old-snapshot.snap');
|
||||
jest
|
||||
.mocked(fs.readFileSync)
|
||||
.mockReturnValue(
|
||||
`// Jest Snapshot v2, ${SNAPSHOT_GUIDE_LINK}\n\n` +
|
||||
'exports[`myKey`] = `<div>\n</div>`;\n',
|
||||
);
|
||||
const update = 'none';
|
||||
|
||||
expect(() => getSnapshotData(filename, update)).toThrow(
|
||||
`${chalk.red(
|
||||
`${chalk.red.bold('Outdated Jest version')}: The version of this ` +
|
||||
'snapshot file indicates that this project is meant to be used ' +
|
||||
'with a newer version of Jest. ' +
|
||||
'The snapshot file version ensures that all developers on a project ' +
|
||||
'are using the same version of Jest. ' +
|
||||
'Please update your version of Jest and re-run the tests.',
|
||||
)}\n\nExpected: v${SNAPSHOT_VERSION}\nReceived: v2`,
|
||||
);
|
||||
});
|
||||
|
||||
test('getSnapshotData() does not throw for when updating', () => {
|
||||
const filename = path.join(__dirname, 'old-snapshot.snap');
|
||||
jest
|
||||
.mocked(fs.readFileSync)
|
||||
.mockReturnValue('exports[`myKey`] = `<div>\n</div>`;\n');
|
||||
const update = 'all';
|
||||
|
||||
expect(() => getSnapshotData(filename, update)).not.toThrow();
|
||||
});
|
||||
|
||||
test('getSnapshotData() marks invalid snapshot dirty when updating', () => {
|
||||
const filename = path.join(__dirname, 'old-snapshot.snap');
|
||||
jest
|
||||
.mocked(fs.readFileSync)
|
||||
.mockReturnValue('exports[`myKey`] = `<div>\n</div>`;\n');
|
||||
const update = 'all';
|
||||
|
||||
expect(getSnapshotData(filename, update)).toMatchObject({dirty: true});
|
||||
});
|
||||
|
||||
test('getSnapshotData() marks valid snapshot not dirty when updating', () => {
|
||||
const filename = path.join(__dirname, 'old-snapshot.snap');
|
||||
jest
|
||||
.mocked(fs.readFileSync)
|
||||
.mockReturnValue(
|
||||
`// Jest Snapshot v${SNAPSHOT_VERSION}, ${SNAPSHOT_GUIDE_LINK}\n\n` +
|
||||
'exports[`myKey`] = `<div>\n</div>`;\n',
|
||||
);
|
||||
const update = 'all';
|
||||
|
||||
expect(getSnapshotData(filename, update)).toMatchObject({dirty: false});
|
||||
});
|
||||
|
||||
test('escaping', () => {
|
||||
const filename = path.join(__dirname, 'escaping.snap');
|
||||
const data = '"\'\\';
|
||||
const writeFileSync = jest.mocked(fs.writeFileSync);
|
||||
|
||||
writeFileSync.mockReset();
|
||||
saveSnapshotFile({key: data}, filename);
|
||||
const writtenData = writeFileSync.mock.calls[0][1];
|
||||
expect(writtenData).toBe(
|
||||
`// Jest Snapshot v1, ${SNAPSHOT_GUIDE_LINK}\n\n` +
|
||||
'exports[`key`] = `"\'\\\\`;\n',
|
||||
);
|
||||
|
||||
// eslint-disable-next-line no-eval
|
||||
const readData = eval(`var exports = {}; ${writtenData} exports`);
|
||||
expect(readData).toEqual({key: data});
|
||||
const snapshotData = readData.key;
|
||||
expect(data).toEqual(snapshotData);
|
||||
});
|
||||
9
frontend/node_modules/@jest/snapshot-utils/src/index.ts
generated
vendored
Normal file
9
frontend/node_modules/@jest/snapshot-utils/src/index.ts
generated
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export * from './utils';
|
||||
export * from './types';
|
||||
8
frontend/node_modules/@jest/snapshot-utils/src/types.ts
generated
vendored
Normal file
8
frontend/node_modules/@jest/snapshot-utils/src/types.ts
generated
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
export type SnapshotData = Record<string, string>;
|
||||
184
frontend/node_modules/@jest/snapshot-utils/src/utils.ts
generated
vendored
Normal file
184
frontend/node_modules/@jest/snapshot-utils/src/utils.ts
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import * as path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import * as fs from 'graceful-fs';
|
||||
import naturalCompare from 'natural-compare';
|
||||
import type {Config} from '@jest/types';
|
||||
import type {SnapshotData} from './types';
|
||||
|
||||
export const SNAPSHOT_VERSION = '1';
|
||||
const SNAPSHOT_VERSION_REGEXP = /^\/\/ Jest Snapshot v(.+),/;
|
||||
export const SNAPSHOT_GUIDE_LINK = 'https://jestjs.io/docs/snapshot-testing';
|
||||
export const SNAPSHOT_VERSION_WARNING = chalk.yellow(
|
||||
`${chalk.bold('Warning')}: Before you upgrade snapshots, ` +
|
||||
'we recommend that you revert any local changes to tests or other code, ' +
|
||||
'to ensure that you do not store invalid state.',
|
||||
);
|
||||
|
||||
const writeSnapshotVersion = () =>
|
||||
`// Jest Snapshot v${SNAPSHOT_VERSION}, ${SNAPSHOT_GUIDE_LINK}`;
|
||||
|
||||
const validateSnapshotVersion = (snapshotContents: string) => {
|
||||
const versionTest = SNAPSHOT_VERSION_REGEXP.exec(snapshotContents);
|
||||
const version = versionTest && versionTest[1];
|
||||
|
||||
if (!version) {
|
||||
return new Error(
|
||||
chalk.red(
|
||||
`${chalk.bold('Outdated snapshot')}: No snapshot header found. ` +
|
||||
'Jest 19 introduced versioned snapshots to ensure all developers ' +
|
||||
'on a project are using the same version of Jest. ' +
|
||||
'Please update all snapshots during this upgrade of Jest.\n\n',
|
||||
) + SNAPSHOT_VERSION_WARNING,
|
||||
);
|
||||
}
|
||||
|
||||
if (version < SNAPSHOT_VERSION) {
|
||||
return new Error(
|
||||
// eslint-disable-next-line prefer-template
|
||||
chalk.red(
|
||||
`${chalk.red.bold('Outdated snapshot')}: The version of the snapshot ` +
|
||||
'file associated with this test is outdated. The snapshot file ' +
|
||||
'version ensures that all developers on a project are using ' +
|
||||
'the same version of Jest. ' +
|
||||
'Please update all snapshots during this upgrade of Jest.',
|
||||
) +
|
||||
'\n\n' +
|
||||
`Expected: v${SNAPSHOT_VERSION}\n` +
|
||||
`Received: v${version}\n\n` +
|
||||
SNAPSHOT_VERSION_WARNING,
|
||||
);
|
||||
}
|
||||
|
||||
if (version > SNAPSHOT_VERSION) {
|
||||
return new Error(
|
||||
// eslint-disable-next-line prefer-template
|
||||
chalk.red(
|
||||
`${chalk.red.bold('Outdated Jest version')}: The version of this ` +
|
||||
'snapshot file indicates that this project is meant to be used ' +
|
||||
'with a newer version of Jest. The snapshot file version ensures ' +
|
||||
'that all developers on a project are using the same version of ' +
|
||||
'Jest. Please update your version of Jest and re-run the tests.',
|
||||
) +
|
||||
'\n\n' +
|
||||
`Expected: v${SNAPSHOT_VERSION}\n` +
|
||||
`Received: v${version}`,
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const normalizeTestNameForKey = (testName: string): string =>
|
||||
testName.replaceAll(/\r\n|\r|\n/g, match => {
|
||||
switch (match) {
|
||||
case '\r\n':
|
||||
return '\\r\\n';
|
||||
case '\r':
|
||||
return '\\r';
|
||||
case '\n':
|
||||
return '\\n';
|
||||
default:
|
||||
return match;
|
||||
}
|
||||
});
|
||||
|
||||
const denormalizeTestNameFromKey = (key: string): string =>
|
||||
key.replaceAll(/\\r\\n|\\r|\\n/g, match => {
|
||||
switch (match) {
|
||||
case '\\r\\n':
|
||||
return '\r\n';
|
||||
case '\\r':
|
||||
return '\r';
|
||||
case '\\n':
|
||||
return '\n';
|
||||
default:
|
||||
return match;
|
||||
}
|
||||
});
|
||||
|
||||
export const testNameToKey = (testName: string, count: number): string =>
|
||||
`${normalizeTestNameForKey(testName)} ${count}`;
|
||||
|
||||
export const keyToTestName = (key: string): string => {
|
||||
if (!/ \d+$/.test(key)) {
|
||||
throw new Error('Snapshot keys must end with a number.');
|
||||
}
|
||||
const testNameWithoutCount = key.replace(/ \d+$/, '');
|
||||
return denormalizeTestNameFromKey(testNameWithoutCount);
|
||||
};
|
||||
|
||||
export const getSnapshotData = (
|
||||
snapshotPath: string,
|
||||
update: Config.SnapshotUpdateState,
|
||||
): {
|
||||
data: SnapshotData;
|
||||
dirty: boolean;
|
||||
} => {
|
||||
const data = Object.create(null);
|
||||
let snapshotContents = '';
|
||||
let dirty = false;
|
||||
|
||||
if (fs.existsSync(snapshotPath)) {
|
||||
try {
|
||||
snapshotContents = fs.readFileSync(snapshotPath, 'utf8');
|
||||
// eslint-disable-next-line no-new-func
|
||||
const populate = new Function('exports', snapshotContents);
|
||||
populate(data);
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const validationResult = validateSnapshotVersion(snapshotContents);
|
||||
const isInvalid = snapshotContents && validationResult;
|
||||
|
||||
if (update === 'none' && isInvalid) {
|
||||
throw validationResult;
|
||||
}
|
||||
|
||||
if ((update === 'all' || update === 'new') && isInvalid) {
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
return {data, dirty};
|
||||
};
|
||||
|
||||
export const escapeBacktickString = (str: string): string =>
|
||||
str.replaceAll(/`|\\|\${/g, '\\$&');
|
||||
|
||||
const printBacktickString = (str: string): string =>
|
||||
`\`${escapeBacktickString(str)}\``;
|
||||
|
||||
export const ensureDirectoryExists = (filePath: string): void => {
|
||||
try {
|
||||
fs.mkdirSync(path.dirname(filePath), {recursive: true});
|
||||
} catch {}
|
||||
};
|
||||
|
||||
export const normalizeNewlines = (string: string): string =>
|
||||
string.replaceAll(/\r\n|\r/g, '\n');
|
||||
|
||||
export const saveSnapshotFile = (
|
||||
snapshotData: SnapshotData,
|
||||
snapshotPath: string,
|
||||
): void => {
|
||||
const snapshots = Object.keys(snapshotData)
|
||||
.sort(naturalCompare)
|
||||
.map(
|
||||
key =>
|
||||
`exports[${printBacktickString(key)}] = ${printBacktickString(
|
||||
normalizeNewlines(snapshotData[key]),
|
||||
)};`,
|
||||
);
|
||||
|
||||
ensureDirectoryExists(snapshotPath);
|
||||
fs.writeFileSync(
|
||||
snapshotPath,
|
||||
`${writeSnapshotVersion()}\n\n${snapshots.join('\n\n')}\n`,
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user