Skip to content

Commit

Permalink
feat: added tests for field extraction functions
Browse files Browse the repository at this point in the history
  • Loading branch information
dosco committed Nov 22, 2024
1 parent d56feaf commit 4818f09
Show file tree
Hide file tree
Showing 4 changed files with 228 additions and 27 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"test": "run-s test:*",
"test:lint": "eslint src --ext .ts",
"test:prettier": "prettier \"src/**/*.ts\" --config .prettierrc --check",
"test:spelling": "cspell \"{README.md,.github/*.md,src/**/*.ts}\"",
"test:spelling": "cspell --quiet \"{README.md,.github/*.md,src/**/*.ts}\"",
"test:unit": "npm run test:unit --workspaces --if-present",
"cov": "run-s build test:unit cov:html cov:lcov && open coverage/index.html",
"doc": "run-s doc:api doc:html",
Expand Down
40 changes: 40 additions & 0 deletions src/ax/dsp/datetime.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import test from 'ava';

import { parseLLMFriendlyDate, parseLLMFriendlyDateTime } from './datetime.js';
import type { AxField } from './sig.js';

const field: AxField = {
name: 'date',
type: { name: 'date', isArray: false }
};

test('datetime parsing with timezone abbr', (t) => {
const dt = parseLLMFriendlyDateTime(field, '2022-01-01 12:00 EST');
t.is(dt.toUTCString(), 'Sat, 01 Jan 2022 17:00:00 GMT');
});

test('datetime parsing with seconds and timezone abbr', (t) => {
const dt = parseLLMFriendlyDateTime(field, '2022-01-01 12:00:10 EST');
t.is(dt.toUTCString(), 'Sat, 01 Jan 2022 17:00:10 GMT');
});

test('datetime parsing with full timezone', (t) => {
const dt = parseLLMFriendlyDateTime(
field,
'2022-01-01 12:00 America/New_York'
);
t.is(dt.toUTCString(), 'Sat, 01 Jan 2022 17:00:00 GMT');
});

test('datetime parsing: invalid datetime value', (t) => {
t.throws(() => parseLLMFriendlyDateTime(field, '2022-01-01 12:00'));
});

test('date parsing', (t) => {
const dt = parseLLMFriendlyDate(field, '2022-01-01');
t.is(dt.toUTCString(), 'Sat, 01 Jan 2022 00:00:00 GMT');
});

test('date parsing: invalid date value', (t) => {
t.throws(() => parseLLMFriendlyDate(field, '2022-01-32'));
});
210 changes: 185 additions & 25 deletions src/ax/dsp/extract.test.ts
Original file line number Diff line number Diff line change
@@ -1,40 +1,200 @@
import test from 'ava';

import { parseLLMFriendlyDate, parseLLMFriendlyDateTime } from './datetime.js';
import type { AxField } from './sig.js';
import {
type extractionState,
extractValues,
streamingExtractFinalValue,
streamingExtractValues
} from './extract.js';
import { AxSignature } from './sig.js';

const field: AxField = {
name: 'date',
type: { name: 'date', isArray: false }
};
// Helper function to create a clean initial state
const createInitialState = (): extractionState => ({
currField: undefined,
s: -1
});

// Tests for extractValues
test('extractValues: extracts single output field', (t) => {
const sig = new AxSignature('inputField1 -> outputField1');
const values: Record<string, unknown> = {};
const content = 'Output Field 1:This is the output content!';

extractValues(sig, values, content);

t.deepEqual(values, { outputField1: 'This is the output content!' });
});

test('datetime parsing with timezone abbr', (t) => {
const dt = parseLLMFriendlyDateTime(field, '2022-01-01 12:00 EST');
t.is(dt.toUTCString(), 'Sat, 01 Jan 2022 17:00:00 GMT');
test('extractValues: extracts multiple output fields', (t) => {
const sig = new AxSignature('input1, input2 -> output1, output2');
const values: Record<string, unknown> = {};
const content = `Output 1: First output content
Output 2: Second\noutput\ncontent`;

extractValues(sig, values, content);

t.deepEqual(values, {
output1: 'First output content',
output2: 'Second\noutput\ncontent'
});
});

test('datetime parsing with seconds and timezone abbr', (t) => {
const dt = parseLLMFriendlyDateTime(field, '2022-01-01 12:00:10 EST');
t.is(dt.toUTCString(), 'Sat, 01 Jan 2022 17:00:10 GMT');
test('extractValues: preserves existing values', (t) => {
const sig = new AxSignature('input1, input2 -> output1, output2');
const values: Record<string, unknown> = {
output1: 'existing content'
};
const content = 'Output 2: New content';

extractValues(sig, values, content);

t.deepEqual(values, {
output1: 'existing content',
output2: 'New content'
});
});

test('datetime parsing with full timezone', (t) => {
const dt = parseLLMFriendlyDateTime(
field,
'2022-01-01 12:00 America/New_York'
);
t.is(dt.toUTCString(), 'Sat, 01 Jan 2022 17:00:00 GMT');
test('extractValues: handles multiline output content', (t) => {
const sig = new AxSignature('input -> output1, output2');
const values: Record<string, unknown> = {};
const content = `Output 1: This is a multi-line
output content for field 1
Output 2:And this is the
multi-line content for field 2`;

extractValues(sig, values, content);

t.deepEqual(values, {
output1: 'This is a multi-line\noutput content for field 1',
output2: 'And this is the\nmulti-line content for field 2'
});
});

// Tests for streamingExtractValues
test('streamingExtractValues: handles streaming output fields', (t) => {
const sig = new AxSignature('input -> output1, outputField2');
const values: Record<string, unknown> = {};
const state = createInitialState();

// First chunk
let content = 'Output 1: First ';
streamingExtractValues(sig, values, state, content);
content += 'output content\n';
streamingExtractValues(sig, values, state, content);
content += 'Output Field 2: Second ';
streamingExtractValues(sig, values, state, content);
content += 'output content';
streamingExtractFinalValue(values, state, content);

t.deepEqual(values, {
output1: 'First output content',
outputField2: 'Second output content'
});
});

test('streamingExtractValues: handles partial output label', (t) => {
const sig = new AxSignature('input -> output1');
const values: Record<string, unknown> = {};
const state = createInitialState();

// Split in middle of "Output" label
let content = 'Out';
streamingExtractValues(sig, values, state, content);
content += 'put 1: Content here';
streamingExtractValues(sig, values, state, content);
streamingExtractFinalValue(values, state, content);

t.deepEqual(values, {
output1: 'Content here'
});
});

test('datetime parsing: invalid datetime value', (t) => {
t.throws(() => parseLLMFriendlyDateTime(field, '2022-01-01 12:00'));
test('handles multiple output occurrences', (t) => {
const sig = new AxSignature('input -> output1, output2');
const values: Record<string, unknown> = {};
const content = `Output: First content
Output: Updated content
Output 1: Final content`;

extractValues(sig, values, content);

// Should use the last occurrence
t.deepEqual(values, {
output1: 'Final content'
});
});

test('date parsing', (t) => {
const dt = parseLLMFriendlyDate(field, '2022-01-01');
t.is(dt.toUTCString(), 'Sat, 01 Jan 2022 00:00:00 GMT');
test('extracts content with various output field formats', (t) => {
const sig = new AxSignature('input -> custom1, custom2, custom3');
const values: Record<string, unknown> = {};
const content = `Custom 1: Generic output
Custom 2: First custom output
Custom 3: Another output`;

extractValues(sig, values, content);

t.deepEqual(values, {
custom1: 'Generic output',
custom2: 'First custom output',
custom3: 'Another output'
});
});

test('date parsing: invalid date value', (t) => {
t.throws(() => parseLLMFriendlyDate(field, '2022-01-32'));
test('streamingExtractValues: handles incremental content with multiple fields', (t) => {
const sig = new AxSignature(
'input -> outputField1, outputField2, outputField3'
);
const values: Record<string, unknown> = {};
const state = createInitialState();

// Send content in chunks
const chunks = [
'Output Field 1: First',
' content here\n',
'Output Field 2: Sec',
'ond content\n',
'Output Field 3: Third content'
];

let content = '';

for (const chunk of chunks) {
content += chunk;
streamingExtractValues(sig, values, state, content);
}
streamingExtractFinalValue(values, state, content);

t.deepEqual(values, {
outputField1: 'First content here',
outputField2: 'Second content',
outputField3: 'Third content'
});
});

test('handles empty and whitespace content', (t) => {
const sig = new AxSignature('input -> output1?, output2?');
const values: Record<string, unknown> = {};
const content = `Output 1:
Output 2:
Output: `;

extractValues(sig, values, content);

t.deepEqual(values, {
output1: undefined,
output2: 'Output:'
});
});

test('error handling for malformed content', (t) => {
const sig = new AxSignature('input -> output1, output2');
const values: Record<string, unknown> = {};

// Content without proper Output: prefix
const malformedContent = 'Some random content without output prefix';

extractValues(sig, values, malformedContent);

// Should not extract any values
t.deepEqual(values, {});
});
3 changes: 2 additions & 1 deletion src/ax/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@
"build:module": "tsc -p tsconfig.build.module.json",
"build:cjs": "tsc -p tsconfig.build.cjs.json",
"watch:build": "tsc -p tsconfig.build.module.json -w",
"test": "ava --verbose",
"test": "run-s test:*",
"test:unit": "ava --verbose",
"tsx": "node --env-file=.env --import=tsx",
"release": "release-it",
"publish": "npm run build && cd build && npm publish",
Expand Down

0 comments on commit 4818f09

Please sign in to comment.