Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
c464b16
Update setting for unit test coverage
jaykim1213 Apr 3, 2026
f24ddc6
Add library cjs tests
jaykim1213 Apr 3, 2026
042a71c
Add analysis unit tests
jaykim1213 Apr 4, 2026
5e7e17d
Add analysis unit tests
jaykim1213 Apr 4, 2026
6db94f8
Add response and component unit tests
jaykim1213 Apr 4, 2026
278f82d
Add utils unit tests
jaykim1213 Apr 4, 2026
23c6ce2
Add more unit tests
jaykim1213 Apr 4, 2026
8de04c8
Add firebase testing
jaykim1213 Apr 4, 2026
d1adf1c
Add a separate supabase test
jaykim1213 Apr 4, 2026
e6deb09
Add unit tests
jaykim1213 Apr 6, 2026
95b77df
Fix bug with firebase testing
jaykim1213 Apr 7, 2026
652eadc
Fix bug
jaykim1213 Apr 7, 2026
4d6a7c4
Merge branch 'dev' into jk/tests
jaykim1213 Apr 7, 2026
98a7a27
Fix import
jaykim1213 Apr 7, 2026
0ac1f7f
Update src/store/hooks/useAuth.spec.tsx
jaykim1213 Apr 7, 2026
666efd1
Update package.json
jaykim1213 Apr 7, 2026
223ed1b
Update src/storage/tests/analysis-manage.spec.ts
jaykim1213 Apr 7, 2026
7a7f664
Update src/storage/tests/analysis-study-summary.spec.ts
jaykim1213 Apr 7, 2026
ca1d691
Fix bugs
jaykim1213 Apr 8, 2026
eb58a8b
Fix bug
jaykim1213 Apr 8, 2026
3bb1410
Update analysis interface tests
jaykim1213 Apr 8, 2026
10ca5ce
Update interface unit tests
jaykim1213 Apr 8, 2026
9c4f4b8
Update response tests
jaykim1213 Apr 8, 2026
a7932a1
Rename files
jaykim1213 Apr 8, 2026
8500202
Update components tests
jaykim1213 Apr 9, 2026
57b35c6
Update unit tests
jaykim1213 Apr 9, 2026
4aac63d
Revert removed tests and update unit tests
jaykim1213 Apr 9, 2026
ab10d3a
Update unit tests
jaykim1213 Apr 9, 2026
a2a259b
Remove unused file
jaykim1213 Apr 9, 2026
146f98a
Remove unused package
jaykim1213 Apr 9, 2026
4e0d0b1
Address PR comments
jaykim1213 Apr 9, 2026
dae4156
Add unit test and fix bug
jaykim1213 Apr 9, 2026
1a0ce53
Add time out
jaykim1213 Apr 9, 2026
6574e31
Merge branch 'dev' into jk/tests
jaykim1213 Apr 16, 2026
7196521
Fix failing tests
jaykim1213 Apr 16, 2026
d3f7e0f
Fix merge errors
jaykim1213 Apr 16, 2026
17a6caf
Fix replay teardown cleanup
JackWilb Apr 21, 2026
06d0c6d
Move tests into sibling test folders
JackWilb Apr 21, 2026
3e091e8
Merge branch 'dev' into jk/tests
jaykim1213 May 12, 2026
db2fdc7
Merge branch 'dev' into jk/tests
JackWilb May 15, 2026
dd68e72
Move some tests and fix some failures
JackWilb May 18, 2026
b277aea
Refactor timestamp handling in FirebaseStorageEngine and update tests…
JackWilb May 18, 2026
73506df
fix: prevent in-memory corruption on reject failure and align Supabas…
JackWilb May 19, 2026
665ade7
fix: useReplay cleanup targets replayRef.current where listeners are …
JackWilb May 19, 2026
53673b5
Merge branch 'dev' into jk/tests
JackWilb May 21, 2026
2d35219
Fix replay timer cleanup separation
JackWilb May 21, 2026
100a1db
Restore claimed slots when undoing rejection
JackWilb May 21, 2026
67ca24b
Tighten component test isolation
JackWilb May 21, 2026
fdb2e76
Add storage rejection regression tests
JackWilb May 21, 2026
c914c6b
Fix storage rejection state rollback
JackWilb May 21, 2026
ba66485
Move component tests into sibling folders
JackWilb May 21, 2026
16e6017
Fix review follow-ups and typecheck
JackWilb May 21, 2026
c91fe65
Merge branch 'dev' into jk/tests
JackWilb May 22, 2026
04c39c3
Merge remote-tracking branch 'origin/dev' into jk/tests
JackWilb Jun 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ How you should interact with the codebase
When working with the ReVISit codebase, work only with the source code files available to you. If you need an external library, please ask for approval first (and include how well used the library is). Make sure to follow best practices for React and TypeScript development, including proper state management, component structuring, and code documentation. Pay extra attention to lifecycle methods and hooks to ensure optimal performance and avoid memory leaks, including any updates to existing code. If you encounter any issues or have suggestions for improvements, feel free to bring them up for discussion. You can run git commands but don't run them unless asked to. Don't interact with GitHub directly. Always check package.json for the scripts available to you for building, testing, and running the project.

Testing
When adding a new feature or modifying code try to maximize unit test coverage. Unit tests should be colocated with the files they are testing, have the same names as the file with .spec., and use the vitest framework. Apply this to both UI/react code as well as non-UI code. For UI code, we use playwright for end-to-end testing. Try to add e2e tests for any new features that involve user interaction. E2E tests are located in the tests/ directory at the root of the project. Don't run `yarn test` directly; instead, describe the tests you want to run, and I'll handle executing them. You can run unittests locally using `yarn unittest`. Preferred commands are those listed in package.json (e.g., `yarn unittest`, `yarn lint`, `yarn typecheck`, `yarn serve`, `yarn build`).
When adding a new feature or modifying code try to maximize unit test coverage. Unit tests should live in a sibling `tests/` folder near the code they are testing, keep the same base name as the tested file with `.spec.`, and use the vitest framework. For example, `src/store/hooks/useReplay.ts` should be tested in `src/store/hooks/tests/useReplay.spec.tsx`. Root-level app specs should live in `src/tests/`. Apply this to both UI/react code as well as non-UI code. For UI code, we use playwright for end-to-end testing. Try to add e2e tests for any new features that involve user interaction. E2E tests are located in the `tests/` directory at the repo root. Don't run `yarn test` directly; instead, describe the tests you want to run, and I'll handle executing them. You can run unittests locally using `yarn unittest`. Preferred commands are those listed in package.json (e.g., `yarn unittest`, `yarn lint`, `yarn typecheck`, `yarn serve`, `yarn build`).

Parser
When adding new features, sometimes it's required to update the parser and the associated types. The parser is located in src/parser/. The parser is responsible for validating and transforming the study config JSON files into a format that the application can use. When updating the parser, ensure that you also update the corresponding types in src/parser/types.ts to reflect any changes made to the study config schema. Make sure to add unit tests for any new parser functionality to ensure its correctness. Additionally, changes to the parser types will require updates to the generated JSON schema files located in src/schemas/. You can regenerate these schema files by running `yarn generate-schemas`.
Expand Down
124 changes: 124 additions & 0 deletions LibraryDocGenerator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import fs from 'fs';
import os from 'os';
import path from 'path';
import { createRequire } from 'module';
import {
afterEach,
describe,
expect,
it,
} from 'vitest';

const require = createRequire(import.meta.url);
const { generateMd, generateLibraryDocs, getLibraries } = require('./libraryDocGenerator.cjs');

describe('libraryDocGenerator', () => {
const tempDirs: string[] = [];

afterEach(() => {
tempDirs.forEach((dir) => {
if (fs.existsSync(dir)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});
tempDirs.length = 0;
});

it('generateMd includes components, sequences, and reference sections', () => {
const md = generateMd('demo-lib', {
description: 'Demo description',
reference: 'Some reference',
doi: '10.1000/xyz',
externalLink: 'https://example.com',
components: { beta: {}, alpha: {} },
sequences: { second: {}, first: {} },
additionalDescription: 'Extra details',
}, true);

expect(md).toContain('# demo-lib');
expect(md).toContain('## Available Components');
expect(md).toContain('- alpha');
expect(md).toContain('- beta');
expect(md).toContain('## Available Sequences');
expect(md).toContain('- first');
expect(md).toContain('- second');
expect(md).toContain('## Reference');
expect(md).toContain('https://dx.doi.org/10.1000/xyz');
expect(md).toContain('## Additional Description');
});

it('generateMd handles example reference text and external-link-only docs links', () => {
const exampleMd = generateMd('demo-lib', {
description: 'Demo description',
reference: 'Some reference',
components: {},
sequences: {},
}, false);

expect(exampleMd).toContain('This is an example study of the library `demo-lib`.');
expect(exampleMd).toContain('Some reference');
expect(exampleMd).not.toContain(':::note[Reference]');

const docsMd = generateMd('demo-lib', {
description: 'Demo description',
externalLink: 'https://example.com',
components: {},
sequences: {},
}, true);

expect(docsMd).toContain('referenceLinks={[');
expect(docsMd).toContain('{name: "demo-lib", url: "https://example.com"}');
expect(docsMd).not.toContain('{name: "DOI"');

const docsWithDoiOnly = generateMd('demo-lib', {
description: 'Demo description',
doi: '10.1000/xyz',
components: {},
sequences: {},
}, true);

expect(docsWithDoiOnly).toContain('{name: "DOI", url: "https://dx.doi.org/10.1000/xyz"}');
expect(docsWithDoiOnly).not.toContain('{name: "demo-lib", url:');
});

it('getLibraries filters hidden entries and .DS_Store entries', () => {
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'lib-doc-list-'));
tempDirs.push(base);
const libsPath = path.join(base, 'public', 'libraries');
fs.mkdirSync(libsPath, { recursive: true });
fs.mkdirSync(path.join(libsPath, 'alpha'));
fs.mkdirSync(path.join(libsPath, '.hidden'));
fs.writeFileSync(path.join(libsPath, '.DS_Store'), '');

expect(getLibraries(libsPath)).toEqual(['alpha']);
});

it('generateLibraryDocs writes docs and example markdown when assets folder exists', () => {
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'lib-doc-run-'));
tempDirs.push(base);
const libraryName = 'alpha';
const librariesPath = path.join(base, 'public', 'libraries', libraryName);
const exampleAssetsPath = path.join(base, 'public', `library-${libraryName}`, 'assets');

fs.mkdirSync(librariesPath, { recursive: true });
fs.mkdirSync(exampleAssetsPath, { recursive: true });
fs.writeFileSync(
path.join(librariesPath, 'config.json'),
JSON.stringify({
description: 'Alpha description',
components: { compA: {} },
sequences: {},
}),
);

generateLibraryDocs(base);

const docsOut = path.join(base, 'docsLibraries', `${libraryName}.md`);
const exampleOut = path.join(exampleAssetsPath, `${libraryName}.md`);

expect(fs.existsSync(docsOut)).toBe(true);
expect(fs.existsSync(exampleOut)).toBe(true);
expect(fs.readFileSync(docsOut, 'utf8')).toContain('# alpha');
expect(fs.readFileSync(exampleOut, 'utf8')).toContain('This is an example study');
});
});
88 changes: 88 additions & 0 deletions LibraryExampleStudyGenerator.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import fs from 'fs';
import os from 'os';
import path from 'path';
import { createRequire } from 'module';
import {
afterEach,
describe,
expect,
it,
vi,
} from 'vitest';

const require = createRequire(import.meta.url);
const {
createExampleConfig,
generateLibraryExamples,
getLibraries,
} = require('./libraryExampleStudyGenerator.cjs');

describe('libraryExampleStudyGenerator', () => {
const tempDirs: string[] = [];

afterEach(() => {
tempDirs.forEach((dir) => {
if (fs.existsSync(dir)) {
fs.rmSync(dir, { recursive: true, force: true });
}
});
tempDirs.length = 0;
});

it('createExampleConfig builds config with expected defaults', () => {
const config = createExampleConfig('my-lib');

expect(config.studyMetadata.title).toBe('my-lib Example Study');
expect(config.importedLibraries).toEqual(['my-lib']);
expect(config.components.introduction.path).toBe('library-my-lib/assets/my-lib.md');
expect(config.sequence.components).toEqual(['introduction']);
});

it('getLibraries filters hidden entries and .DS_Store entries', () => {
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'lib-example-list-'));
tempDirs.push(base);
const libsPath = path.join(base, 'public', 'libraries');
fs.mkdirSync(libsPath, { recursive: true });
fs.mkdirSync(path.join(libsPath, 'alpha'));
fs.mkdirSync(path.join(libsPath, '.hidden'));
fs.writeFileSync(path.join(libsPath, '.DS_Store'), '');

expect(getLibraries(libsPath)).toEqual(['alpha']);
});

it('generateLibraryExamples creates missing example study and invokes doc generation command', () => {
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'lib-example-run-'));
tempDirs.push(base);
const libraryName = 'alpha';
const librariesPath = path.join(base, 'public', 'libraries', libraryName);
fs.mkdirSync(librariesPath, { recursive: true });

const generateDocsFn = vi.fn();
generateLibraryExamples(base, generateDocsFn);

const examplePath = path.join(base, 'public', `library-${libraryName}`);
const configPath = path.join(examplePath, 'config.json');
const assetsPath = path.join(examplePath, 'assets');

expect(fs.existsSync(examplePath)).toBe(true);
expect(fs.existsSync(assetsPath)).toBe(true);
expect(fs.existsSync(configPath)).toBe(true);
expect(generateDocsFn).toHaveBeenCalledTimes(1);
expect(generateDocsFn).toHaveBeenCalledWith(base);
});

it('logs an error when doc generation throws', () => {
const base = fs.mkdtempSync(path.join(os.tmpdir(), 'lib-example-error-'));
tempDirs.push(base);
fs.mkdirSync(path.join(base, 'public', 'libraries', 'alpha'), { recursive: true });
const errorSpy = vi.spyOn(console, 'error').mockImplementation(() => undefined);
const generateDocsFn = vi.fn(() => {
throw new Error('test Error');
});

generateLibraryExamples(base, generateDocsFn);

expect(errorSpy).toHaveBeenCalledWith(expect.stringContaining('Error running libraryDocGenerator.cjs: Error: test Error'));
errorSpy.mockRestore();
});
});
66 changes: 38 additions & 28 deletions libraryDocGenerator.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -54,40 +54,50 @@ import StructuredLinks from '@site/src/components/StructuredLinks/StructuredLink
/>` : ''}
`;

const librariesPath = path.join(__dirname, './public/libraries');
const docsLibrariesPath = path.join(__dirname, './docsLibraries');

const libraries = fs.readdirSync(librariesPath)
const getLibraries = (libsPath) => fs.readdirSync(libsPath)
.filter((library) => !library.startsWith('.') && !library.endsWith('.DS_Store'));

if (!fs.existsSync(docsLibrariesPath)) {
fs.mkdirSync(docsLibrariesPath);
}
const generateLibraryDocs = (base) => {
const librariesPath = path.join(base, 'public', 'libraries');
const docsLibrariesPath = path.join(base, 'docsLibraries');

libraries.forEach((library) => {
const libraryPath = path.join(librariesPath, library, 'config.json');
const libraryConfig = JSON.parse(fs.readFileSync(libraryPath, 'utf8'));
const libraries = getLibraries(librariesPath);

const docsMd = generateMd(library, libraryConfig, true);
const exampleMd = generateMd(library, libraryConfig, false);
if (!fs.existsSync(docsLibrariesPath)) {
fs.mkdirSync(docsLibrariesPath);
}

// Save to docsLibraries folder
const docsLibraryPath = path.join(docsLibrariesPath, `${library}.md`);
fs.writeFileSync(docsLibraryPath, docsMd);
// eslint-disable-next-line no-console
console.log(`Documentation saved to ${docsLibraryPath}`);
libraries.forEach((library) => {
const libraryPath = path.join(librariesPath, library, 'config.json');
const libraryConfig = JSON.parse(fs.readFileSync(libraryPath, 'utf8'));

// Save to example study assets folder if assets folder exists
// Add a prefix to baseMarkdown when saving to example assets
const exampleAssetsPath = path.join(__dirname, 'public', `library-${library}`, 'assets');
if (fs.existsSync(exampleAssetsPath)) {
const exampleDocsPath = path.join(exampleAssetsPath, `${library}.md`);
fs.writeFileSync(exampleDocsPath, exampleMd);
const docsMd = generateMd(library, libraryConfig, true);
const exampleMd = generateMd(library, libraryConfig, false);

// Save to docsLibraries folder
const docsLibraryPath = path.join(docsLibrariesPath, `${library}.md`);
fs.writeFileSync(docsLibraryPath, docsMd);
// eslint-disable-next-line no-console
console.log(`Documentation saved to ${exampleDocsPath}`);
}
});
console.log(`Documentation saved to ${docsLibraryPath}`);

// Save to example study assets folder if assets folder exists
// Add a prefix to baseMarkdown when saving to example assets
const exampleAssetsPath = path.join(base, 'public', `library-${library}`, 'assets');
if (fs.existsSync(exampleAssetsPath)) {
const exampleDocsPath = path.join(exampleAssetsPath, `${library}.md`);
fs.writeFileSync(exampleDocsPath, exampleMd);

// eslint-disable-next-line no-console
console.log(`Documentation saved to ${exampleDocsPath}`);
}
});

// eslint-disable-next-line no-console
console.log('Library documentation generated');
};

if (require.main === module) {
generateLibraryDocs(__dirname);
}

// eslint-disable-next-line no-console
console.log('Library documentation generated');
module.exports = { generateMd, getLibraries, generateLibraryDocs };
Loading
Loading