mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
test(assistant): shared card tests, wiki rows and ratchets after the card packages
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
* ask for a change only for someone who may make it, and the answer's own
|
||||
* when none is left for a reader.
|
||||
*/
|
||||
import type { AssistantMessage } from '@trek/shared';
|
||||
import type { AssistantMessage, TrekCard } from '@trek/shared';
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
|
||||
import { followUpsFor } from './assistantFollowUpRules';
|
||||
@@ -19,7 +19,7 @@ const answer: AssistantMessage = {
|
||||
parts: [
|
||||
{ type: 'text', text: 'Here is your year.' },
|
||||
{ type: 'tool-call', toolCallId: 'r1', toolName: 'get_vacay_plan', args: {}, result: {} },
|
||||
{ type: 'data-trek-card', data: { toolCallId: 'r1', card: { kind: 'vacay' } } },
|
||||
{ type: 'data-trek-card', data: { toolCallId: 'r1', card: { kind: 'vacay' } as TrekCard } },
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -304,8 +304,8 @@ describe('messageBlocks: found places and weather', () => {
|
||||
});
|
||||
|
||||
describe('messageBlocks: cards', () => {
|
||||
const ROUTE: TrekCard = { kind: 'tourRoute' };
|
||||
const LIST: TrekCard = { kind: 'tourList' };
|
||||
const ROUTE = { kind: 'tourRoute' } as TrekCard;
|
||||
const LIST = { kind: 'tourList' } as TrekCard;
|
||||
const card = (toolCallId: string, value: TrekCard | null): AssistantMessagePart => ({
|
||||
type: 'data-trek-card',
|
||||
data: { toolCallId, card: value },
|
||||
|
||||
@@ -30,11 +30,15 @@ describe('assistantShowOnMap', () => {
|
||||
|
||||
it('FE-ASSIST-SHOW-003: no target where nothing shows on the map; a place without a spot opens the planner', () => {
|
||||
expect(showOnMapTarget(applied('packing', [2], null))).toBeNull();
|
||||
expect(showOnMapTarget(applied('tour', [2], 3))).toBeNull();
|
||||
expect(showOnMapTarget(applied('tour', [], 3))).toBeNull();
|
||||
expect(showOnMapTarget(applied('memory', [], null))).toBeNull();
|
||||
expect(searchOf(applied('place', [], null))).toBe('');
|
||||
});
|
||||
|
||||
it('FE-ASSIST-SHOW-005: an applied tour goes to its line on the plan, by the place it is', () => {
|
||||
expect(searchOf(applied('tour', [2], 3))).toBe('?place=2');
|
||||
});
|
||||
|
||||
it('FE-ASSIST-SHOW-004: a tab of the trip goes as ?tab= with what the tab reads beside it', () => {
|
||||
const expense = plannerSearchOf({ kind: 'tab', tab: 'finanzplan', params: { expense: '7' } });
|
||||
expect(expense).toBe('?tab=finanzplan&expense=7');
|
||||
|
||||
@@ -17,6 +17,8 @@ vi.mock('./cardViews', async (importOriginal) => {
|
||||
const { createElement } = await import('react');
|
||||
const views = {
|
||||
...actual.CARD_VIEWS,
|
||||
// A kind left without a view, whatever its package draws.
|
||||
vacay: {},
|
||||
tourList: {
|
||||
Block: function Block({ cards }: { cards: readonly { toolCallId: string }[] }) {
|
||||
return createElement('p', null, `${cards.length} tour lists`);
|
||||
@@ -50,12 +52,12 @@ afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
describe('AssistantCardBlock', () => {
|
||||
it('FE-AI-CARDBLOCK-001: the kind draws the whole block', () => {
|
||||
render(<AssistantCardBlock block={block({ kind: 'tourList' }, 2)} message={MESSAGE} />);
|
||||
render(<AssistantCardBlock block={block({ kind: 'tourList' } as TrekCard, 2)} message={MESSAGE} />);
|
||||
expect(screen.getByText('2 tour lists')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('FE-AI-CARDBLOCK-002: a kind not drawn yet shows nothing', () => {
|
||||
const { container } = render(<AssistantCardBlock block={block({ kind: 'vacay' })} message={MESSAGE} />);
|
||||
const { container } = render(<AssistantCardBlock block={block({ kind: 'vacay' } as TrekCard)} message={MESSAGE} />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
@@ -63,7 +65,7 @@ describe('AssistantCardBlock', () => {
|
||||
const logged = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||
const { container } = render(
|
||||
<div>
|
||||
<AssistantCardBlock block={block({ kind: 'tourRoute' })} message={MESSAGE} />
|
||||
<AssistantCardBlock block={block({ kind: 'tourRoute' } as TrekCard)} message={MESSAGE} />
|
||||
<span>the answer</span>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -64,9 +64,9 @@ vi.mock('./cardViews', async (importOriginal) => {
|
||||
|
||||
const TRIP = 4;
|
||||
const INITIAL = useAssistantStore.getState();
|
||||
const ROUTE: TrekCard = { kind: 'tourRoute' };
|
||||
const LIST: TrekCard = { kind: 'tourList' };
|
||||
const BROKEN: TrekCard = { kind: 'dayRoute' };
|
||||
const ROUTE = { kind: 'tourRoute' } as TrekCard;
|
||||
const LIST = { kind: 'tourList' } as TrekCard;
|
||||
const BROKEN = { kind: 'dayRoute' } as TrekCard;
|
||||
|
||||
function preview(toolCallId: string, subject: string): AssistantChangePreview {
|
||||
return {
|
||||
|
||||
@@ -11,7 +11,9 @@ import {
|
||||
ASSISTANT_CHANGE_VERB_KEYS,
|
||||
type AssistantApplied,
|
||||
type AssistantChangeVerbKey,
|
||||
type TrekCard,
|
||||
type TrekCardKind,
|
||||
type TrekCardOf,
|
||||
trekCardSchema,
|
||||
} from '@trek/shared';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
@@ -32,6 +34,10 @@ import type { CardLook } from './cardTypes';
|
||||
import { CARD_VIEWS } from './cardViews';
|
||||
|
||||
const KINDS = trekCardSchema.options.map((option) => option.shape.kind.value) as TrekCardKind[];
|
||||
/** A vacation card cut down to its kind: the layout and map rules read nothing else of it. */
|
||||
const VACAY = { kind: 'vacay' } as TrekCard;
|
||||
/** A day route cut down to its kind, for the same reason. */
|
||||
const DAY_ROUTE = { kind: 'dayRoute' } as TrekCardOf<'dayRoute'>;
|
||||
|
||||
describe('cardLooks', () => {
|
||||
it('FE-AI-LOOK-001: every verb is named by its own key', () => {
|
||||
@@ -46,17 +52,22 @@ describe('cardLooks', () => {
|
||||
expect(STEP_LOOKS.get_trip_summary?.stepKey).toBe('assistant.step.reading');
|
||||
});
|
||||
|
||||
it('FE-AI-LOOK-003: only a place, a stop, a note and a booking show on the map, as before the cards', () => {
|
||||
it('FE-AI-LOOK-003: a place, a stop, a note, a booking and a stay show on the map; a kept note never', () => {
|
||||
const onMap = Object.entries(ENTITY_LOOK).flatMap(([entity, look]) => (look.onMap ? [entity] : []));
|
||||
expect(onMap.sort()).toEqual(['assignment', 'day_note', 'place', 'reservation']);
|
||||
expect(Object.values(ENTITY_LOOK).every((look) => !look.open)).toBe(true);
|
||||
// The core's own; a part of TREK may add its entities (a stay shows its place).
|
||||
expect(onMap).toEqual(expect.arrayContaining(['assignment', 'day_note', 'place', 'reservation', 'accommodation']));
|
||||
expect(onMap).not.toContain('memory');
|
||||
expect(onMap).not.toContain('other');
|
||||
for (const entity of ['place', 'assignment', 'day_note', 'memory', 'other'] as const) {
|
||||
expect(ENTITY_LOOK[entity].open).toBeUndefined();
|
||||
}
|
||||
});
|
||||
|
||||
it('FE-AI-LOOK-004: every kind has a look and a view; a pending view groups by kind and gives way to nothing', () => {
|
||||
expect(Object.keys(CARD_LOOKS).sort()).toEqual([...KINDS].sort());
|
||||
expect(Object.keys(CARD_VIEWS).sort()).toEqual([...KINDS].sort());
|
||||
expect(CARD_RULES.group({ kind: 'vacay' })).toBe('vacay');
|
||||
expect(CARD_RULES.yieldsToChange({ kind: 'expenses' }, { kind: 'payments' })).toBe(false);
|
||||
expect(CARD_RULES.group(VACAY)).toBe('vacay');
|
||||
expect(CARD_RULES.yieldsToChange({ kind: 'costPerPerson' } as TrekCard, VACAY)).toBe(false);
|
||||
});
|
||||
|
||||
it('FE-AI-LOOK-005: a kind draws its lines and points on the map through its look', () => {
|
||||
@@ -66,10 +77,10 @@ describe('cardLooks', () => {
|
||||
],
|
||||
ghosts: (): GhostPoint[] => [{ id: 'via', lat: 45.1, lng: 7.2, name: 'Col', variant: 'point' }],
|
||||
};
|
||||
expect(look.lines?.({ kind: 'dayRoute' }, 'before')).toEqual([
|
||||
expect(look.lines?.(DAY_ROUTE, 'before')).toEqual([
|
||||
{ id: 'day:before', lines: [[[45.1, 7.2]]], color: 'var(--accent)', dashed: true },
|
||||
]);
|
||||
expect(look.ghosts?.({ kind: 'dayRoute' })[0]?.variant).toBe('point');
|
||||
expect(look.ghosts?.(DAY_ROUTE)[0]?.variant).toBe('point');
|
||||
});
|
||||
|
||||
it("FE-AI-LOOK-006: the layout rules and the map read a kind's look, nothing for one that says nothing", () => {
|
||||
@@ -78,19 +89,17 @@ describe('cardLooks', () => {
|
||||
route.yieldsToChange = (_answer, change) => change.kind === 'dayRoute';
|
||||
route.lines = (_card, use) => [{ id: `day:${use}`, lines: [], color: 'var(--accent)' }];
|
||||
try {
|
||||
expect(CARD_RULES.group({ kind: 'dayRoute' })).toBe('routes');
|
||||
expect(CARD_RULES.yieldsToChange({ kind: 'dayRoute' }, { kind: 'dayRoute' })).toBe(true);
|
||||
expect(CARD_MAP_LOOK.lines({ kind: 'dayRoute' }, 'look')).toEqual([
|
||||
{ id: 'day:look', lines: [], color: 'var(--accent)' },
|
||||
]);
|
||||
expect(CARD_RULES.group(DAY_ROUTE)).toBe('routes');
|
||||
expect(CARD_RULES.yieldsToChange(DAY_ROUTE, DAY_ROUTE)).toBe(true);
|
||||
expect(CARD_MAP_LOOK.lines(DAY_ROUTE, 'look')).toEqual([{ id: 'day:look', lines: [], color: 'var(--accent)' }]);
|
||||
} finally {
|
||||
delete route.group;
|
||||
delete route.yieldsToChange;
|
||||
delete route.lines;
|
||||
}
|
||||
expect(CARD_MAP_LOOK.lines({ kind: 'dayRoute' }, 'look')).toEqual([]);
|
||||
expect(CARD_MAP_LOOK.ghosts({ kind: 'vacay' })).toEqual([]);
|
||||
expect(CARD_MAP_LOOK.hidesEffectGhost({ kind: 'vacay' })).toBe(false);
|
||||
expect(CARD_MAP_LOOK.lines(DAY_ROUTE, 'look')).toEqual([]);
|
||||
expect(CARD_MAP_LOOK.ghosts(VACAY)).toEqual([]);
|
||||
expect(CARD_MAP_LOOK.hidesEffectGhost(VACAY)).toBe(false);
|
||||
});
|
||||
|
||||
it('FE-AI-LOOK-007: a verb or entity only a newer server knows looks like a plain update or an other', () => {
|
||||
|
||||
Reference in New Issue
Block a user