updates on the design and chatbot fix
This commit is contained in:
119
tests/assistant/pageSuggestions.test.js
Normal file
119
tests/assistant/pageSuggestions.test.js
Normal file
@@ -0,0 +1,119 @@
|
||||
import { getPageContext, CHIP_LABELS } from '@/components/assistant/DoormileAI/pageContext';
|
||||
import { resolveIntentId } from '@/components/assistant/intents';
|
||||
|
||||
/**
|
||||
* Every suggestion a page offers must resolve — and must resolve to the
|
||||
* intent that page means.
|
||||
*
|
||||
* `pageContext.jsx` states the rule at the top of the file: "Every suggestion
|
||||
* here MUST be a phrasing the deterministic matcher in intents.js actually
|
||||
* resolves — a suggestion chip that returns 'I can't answer that yet' is worse
|
||||
* than no chip." It was enforced by reading. It is now enforced here.
|
||||
*
|
||||
* Resolving is not enough on its own, because INTENTS is an ordered catalog
|
||||
* and the FIRST match wins. A question can match the intent it was written for
|
||||
* and still be answered by a different one that happens to run earlier —
|
||||
* `ROADMAP.md` B1/B2 are two shipped instances of exactly that. So these tests
|
||||
* assert on which intent actually wins, not merely that something does.
|
||||
*/
|
||||
|
||||
// The route of every page that carries the assistant. Sourced from App.jsx.
|
||||
const PAGES = [
|
||||
'/doormile/orders',
|
||||
'/doormile/bookings',
|
||||
'/doormile/deliveries',
|
||||
'/doormile/control-x',
|
||||
'/doormile/milers',
|
||||
'/doormile/vehicles',
|
||||
'/doormile/hubs',
|
||||
'/doormile/tenants',
|
||||
'/doormile/customers',
|
||||
'/doormile/tripsheets',
|
||||
'/doormile/exceptions',
|
||||
'/doormile/pricing',
|
||||
'/doormile/app-users',
|
||||
'/doormile/competitive-intel',
|
||||
'/doormile/reports'
|
||||
];
|
||||
|
||||
/** The intent the ordered catalog actually reaches for a question. */
|
||||
const winningIntent = (text) => resolveIntentId(text);
|
||||
|
||||
const chipsFor = (route) => getPageContext(route).suggestions;
|
||||
|
||||
describe('every page suggestion resolves to an intent', () => {
|
||||
PAGES.forEach((route) => {
|
||||
it(`${route}`, () => {
|
||||
const chips = chipsFor(route);
|
||||
expect(chips.length).toBeGreaterThan(0);
|
||||
chips.forEach((chip) => {
|
||||
expect(winningIntent(chip.text)).not.toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('a page offers its own subject, not another page\'s', () => {
|
||||
// The intent id a page's questions are allowed to reach. This is the actual
|
||||
// separation being asked for: on Orders, order intents; on Deliveries,
|
||||
// delivery intents. Not "an intent exists" — the RIGHT one.
|
||||
const ALLOWED = {
|
||||
'/doormile/orders': /order|repeat|create|assign|summary|revenue|delay/i,
|
||||
'/doormile/bookings': /order|delay/i,
|
||||
'/doormile/deliveries': /consignment|order|delay|rider/i,
|
||||
'/doormile/control-x': /order|batch|rider|assign|delay/i,
|
||||
'/doormile/milers': /rider|assign/i,
|
||||
'/doormile/vehicles': /vehicle/i,
|
||||
'/doormile/hubs': /hub/i,
|
||||
'/doormile/tenants': /tenant|partner/i,
|
||||
'/doormile/customers': /customer/i,
|
||||
'/doormile/tripsheets': /tripsheet/i,
|
||||
'/doormile/exceptions': /exception|delay/i,
|
||||
'/doormile/pricing': /pricing/i,
|
||||
'/doormile/app-users': /appuser/i,
|
||||
'/doormile/competitive-intel': /competitor|carrier/i
|
||||
};
|
||||
|
||||
Object.entries(ALLOWED).forEach(([route, allowed]) => {
|
||||
it(`${route} stays on topic`, () => {
|
||||
chipsFor(route).forEach((chip) => {
|
||||
const id = winningIntent(chip.text);
|
||||
expect(`${route} · "${chip.text}" → ${id}`).toMatch(allowed);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('the two bugs this change fixes', () => {
|
||||
it('Deliveries is no longer the Orders list wearing a different name', () => {
|
||||
const deliveries = chipsFor('/doormile/deliveries').map((c) => c.text);
|
||||
const orders = chipsFor('/doormile/orders').map((c) => c.text);
|
||||
expect(deliveries).not.toEqual(orders);
|
||||
// It used to be `{ ...ORDERS, label: 'Deliveries' }`, so a page of
|
||||
// consignments offered "Create an order" and "Repeat yesterday's orders".
|
||||
expect(deliveries).not.toContain('Create an order');
|
||||
expect(deliveries).not.toContain("Repeat yesterday's orders");
|
||||
expect(deliveries).toContain('How many consignments do we have?');
|
||||
});
|
||||
|
||||
it('"carrier pricing rules" would be answered by the WRONG intent', () => {
|
||||
// Kept as a live assertion rather than a comment. carrierPricingCount
|
||||
// documents this exact phrasing for itself, and pricingCount matches
|
||||
// /\bpricing\b/ 57 lines earlier, so the catalog never reaches it.
|
||||
expect(winningIntent('How many carrier pricing rules?')).toBe('pricingCount');
|
||||
// Which is why Competitive Intel asks it the other way.
|
||||
expect(winningIntent('How many carriers do we have?')).toBe('carrierPricingCount');
|
||||
});
|
||||
});
|
||||
|
||||
describe('chips are readable in a 393px panel', () => {
|
||||
it('every suggestion has a short label', () => {
|
||||
const missing = [];
|
||||
PAGES.forEach((route) => {
|
||||
chipsFor(route).forEach((chip) => {
|
||||
if (!CHIP_LABELS[chip.text]) missing.push(`${route} · ${chip.text}`);
|
||||
});
|
||||
});
|
||||
expect(missing).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user