Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 35 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,38 @@ We use project references between each of our packages and apps.

- We always want to make sure that we are updating the root `tsconfig.json` file to reference any new or renamed package or app in our monorepo
- We always want to make sure that if a package has a dependency on another `workspace:` package, that the dependent package is added to the `references` block of the consuming package. This ensures fast and accurate type checking without extra work across all packages.

## Testing

We use Bun's built-in testing framework for unit tests. Tests are located in a `test/` folder within each package, separate from the source code.

### Running Tests

For the precision-diffs package:

```bash
# From the package directory
bun test

# From the monorepo root
bun run diffs:test
```

### Updating Snapshots

When test snapshots need to be updated:

```bash
# From the package directory
bun test --update-snapshots

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we let the AI know about this? Could that result in them seeing a failing test and then going oh, well, i'll just update the snapshots to fix the problem?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these are like an industry standard, so AI should know when to do it and when to not.


# From the monorepo root
bun run diffs:update-snapshots
```

### Test Structure

- Tests use Bun's native `describe`, `test`, and `expect` from `bun:test`
- Snapshot testing is supported natively via `toMatchSnapshot()`
- Shared test fixtures and mocks are located in `test/mocks.ts`
- Test files are included in TypeScript type checking via `tsconfig.json`
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ bun install
bun run dev
```

## Testing

Run tests for the precision-diffs package:

```bash
# Run tests
bun run diffs:test

# Update snapshots
bun run diffs:update-snapshots
```

## Publishing precision diffs

```sh
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"demo:tsc": "(cd apps/demo && bun run tsc)",
"diffs:build": "(cd packages/precision-diffs && bun run build)",
"diffs:dev": "(cd packages/precision-diffs && bun run dev)",
"diffs:test": "(cd packages/precision-diffs && bun run test)",
"diffs:update-snapshots": "(cd packages/precision-diffs && bun test --update-snapshots)",
"diffs:tsc": "(cd packages/precision-diffs && bun run tsc)",
"docs:build": "(cd apps/docs && bun run build)",
"docs:dev": "(cd apps/docs && bun run dev)",
Expand Down
23 changes: 23 additions & 0 deletions packages/precision-diffs/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
# @pierre/precision-diffs

Docs at [https://pierrejs-docs.vercel.app/](https://pierrejs-docs.vercel.app/)

## Development

### Building

```bash
bun run build
```

### Testing

```bash
# Run tests
bun test

# Update snapshots
bun test --update-snapshots

# Type checking
bun run tsc
```

Tests are located in the `test/` folder and use Bun's native testing framework with snapshot support.
1 change: 1 addition & 0 deletions packages/precision-diffs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scripts": {
"build": "tsdown --clean",
"dev": "echo 'Watching for changes…' && tsdown --watch --log-level error",
"test": "bun test",
"tsc": "tsc --noEmit --pretty",
"prepublishOnly": "bun run build"
},
Expand Down
144 changes: 144 additions & 0 deletions packages/precision-diffs/test/FileRenderer.ast.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { describe, expect, test } from 'bun:test';
import type { Element, ElementContent } from 'hast';

import { FileRenderer } from '../src/FileRenderer';
import { mockFiles } from './mocks';

describe('FileRenderer AST Structure', () => {
test('should generate correct AST structure for JavaScript file', async () => {
const instance = new FileRenderer();
const result = await instance.render(mockFiles.file2);

expect(result).toBeDefined();
const { codeAST, totalLines } = result!;

// Verify line count matches
const inputLines = mockFiles.file2.contents.split('\n').length;
expect(totalLines).toBe(inputLines);
expect(codeAST.length).toBe(inputLines);

// Verify each line has the expected structure
for (let i = 0; i < codeAST.length; i++) {
const lineElement = codeAST[i] as Element;

// Each line should be a div element
expect(lineElement.type).toBe('element');
expect(lineElement.tagName).toBe('div');

// Each line should have the correct properties
expect(lineElement.properties).toBeDefined();
expect(lineElement.properties['data-line']).toBe(i + 1);
expect(lineElement.properties['data-line-type']).toBe('context');
expect(lineElement.properties['data-line-index']).toBe(i);

// Each line should have two child spans: column-number and column-content
expect(lineElement.children).toBeDefined();
expect(lineElement.children.length).toBe(2);

const [numberColumn, contentColumn] = lineElement.children as Element[];

// Verify line number column
expect(numberColumn.type).toBe('element');
expect(numberColumn.tagName).toBe('span');
expect(numberColumn.properties['data-column-number']).toBe('');

// Verify content column
expect(contentColumn.type).toBe('element');
expect(contentColumn.tagName).toBe('span');
expect(contentColumn.properties['data-column-content']).toBe('');
}
});

test('should apply syntax highlighting with CSS variables', async () => {
const instance = new FileRenderer();
const result = await instance.render(mockFiles.file2);

expect(result).toBeDefined();
const { codeAST } = result!;

// Helper to recursively find all text nodes with their parent styles
const findTextNodesWithStyles = (
nodes: ElementContent[]
): Array<{ text: string; style?: string }> => {
const results: Array<{ text: string; style?: string }> = [];

const traverse = (node: ElementContent, parentStyle?: string) => {
if (node.type === 'text') {
results.push({ text: node.value, style: parentStyle });
} else if (node.type === 'element') {
const style =
typeof node.properties?.style === 'string'
? node.properties.style
: undefined;
node.children?.forEach((child) =>
traverse(child, style ?? parentStyle)
);
}
};

nodes.forEach((node) => traverse(node));
return results;
};

const textNodes = findTextNodesWithStyles(codeAST);

// Verify that at least some tokens have syntax highlighting with CSS variables
const styledTokens = textNodes.filter((node) => node.style !== undefined);
expect(styledTokens.length).toBeGreaterThan(0);

// Verify that styled tokens have the expected CSS variable format
const tokensWithCSSVars = styledTokens.filter(
(node) =>
node.style?.match(
/--pjs-dark:#[A-F0-9]{6};--pjs-light:#[A-F0-9]{6}/
) !== null
);
expect(tokensWithCSSVars.length).toBeGreaterThan(0);

// Verify specific keyword exists and is highlighted
const functionToken = textNodes.find((node) => node.text === 'function');
expect(functionToken).toBeDefined();
expect(functionToken!.style).toBeDefined();
expect(functionToken!.style).toMatch(
/--pjs-dark:#[A-F0-9]{6};--pjs-light:#[A-F0-9]{6}/
);
});

test('should generate correct totalLines count', async () => {
const instance = new FileRenderer();

// Test file1 (TypeScript)
const result1 = await instance.render(mockFiles.file1);
expect(result1).toBeDefined();
const file1Lines = mockFiles.file1.contents.split('\n').length;
expect(result1!.totalLines).toBe(file1Lines);

// Test file2 (JavaScript)
const result2 = await instance.render(mockFiles.file2);
expect(result2).toBeDefined();
const file2Lines = mockFiles.file2.contents.split('\n').length;
expect(result2!.totalLines).toBe(file2Lines);
});

test('should include CSS property in result', async () => {
const instance = new FileRenderer();
const result = await instance.render(mockFiles.file2);

expect(result).toBeDefined();
expect(result!.css).toBeDefined();
expect(typeof result!.css).toBe('string');
// CSS may be empty string depending on theme configuration
});

test('should create preNode with correct properties', async () => {
const instance = new FileRenderer();
const result = await instance.render(mockFiles.file2);

expect(result).toBeDefined();
const { preNode } = result!;

expect(preNode.type).toBe('element');
expect(preNode.tagName).toBe('pre');
expect(preNode.properties).toBeDefined();
});
});
14 changes: 14 additions & 0 deletions packages/precision-diffs/test/FileRenderer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { describe, expect, test } from 'bun:test';

import { FileRenderer } from '../src/FileRenderer';
import { mockFiles } from './mocks';

describe('FileRenderer', () => {
test('should render TypeScript code to AST matching snapshot', async () => {
const instance = new FileRenderer();
const result = await instance.render(mockFiles.file1);

expect(result).toBeDefined();
expect(result?.codeAST).toMatchSnapshot();
});
});
Loading