feat: expand ISAC content modeling
This commit is contained in:
@@ -0,0 +1,186 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { createDraft, type LoadoutDraft } from '$lib/builder/loadout';
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
insertBuild: vi.fn(),
|
||||
getSessionUser: vi.fn(async () => ({
|
||||
id: 'agent-1',
|
||||
displayName: 'Test Agent',
|
||||
})),
|
||||
database: {
|
||||
prepare: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('$lib/server/auth/session', () => ({
|
||||
getSessionUser: mocks.getSessionUser,
|
||||
}));
|
||||
|
||||
vi.mock('$lib/server/database', () => ({
|
||||
getDatabase: vi.fn(async () => mocks.database),
|
||||
}));
|
||||
|
||||
import { actions } from './+page.server';
|
||||
|
||||
const primaryStatId = 'skill-duration';
|
||||
const secondaryStatIds = ['critical-hit-chance', 'critical-hit-damage'];
|
||||
|
||||
const validLoadout = (): LoadoutDraft => {
|
||||
const loadout = createDraft();
|
||||
loadout.buildName = 'Validated Skill Chips';
|
||||
loadout.specialization.focusId = 'focus-1';
|
||||
loadout.chips[0] = {
|
||||
setId: 'chip-set-1',
|
||||
quality: 'Superior',
|
||||
primary: { statId: primaryStatId, value: 15 },
|
||||
secondary: [
|
||||
{ statId: secondaryStatIds[0], value: 2 },
|
||||
{ statId: secondaryStatIds[1], value: 4 },
|
||||
],
|
||||
};
|
||||
return loadout;
|
||||
};
|
||||
|
||||
const saveRequest = (loadout: LoadoutDraft) => {
|
||||
const form = new FormData();
|
||||
form.set('loadout', JSON.stringify(loadout));
|
||||
form.set('visibility', 'private');
|
||||
return new Request('http://localhost/builder?/save', {
|
||||
method: 'POST',
|
||||
body: form,
|
||||
});
|
||||
};
|
||||
|
||||
const runSave = (loadout: LoadoutDraft) =>
|
||||
actions.save?.({
|
||||
cookies: {} as never,
|
||||
request: saveRequest(loadout),
|
||||
} as never);
|
||||
|
||||
describe('Builder Skill Chip save validation', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
mocks.database.prepare.mockImplementation(async (sql: string) => ({
|
||||
all: vi.fn(async () => {
|
||||
if (sql.includes('FROM skill_chip_sets'))
|
||||
return [
|
||||
{
|
||||
id: 'chip-set-1',
|
||||
specialization_ability_id: 'ability-1',
|
||||
},
|
||||
];
|
||||
if (sql.includes('FROM specialization_focus_abilities'))
|
||||
return [{ ability_id: 'ability-1', ability_slot: 1 }];
|
||||
if (sql.includes('FROM skill_chip_primary_options'))
|
||||
return [{ stat_attribute_id: primaryStatId }];
|
||||
if (sql.includes('FROM skill_chip_primary_quality_values'))
|
||||
return [
|
||||
{
|
||||
stat_attribute_id: primaryStatId,
|
||||
quality: 'Superior',
|
||||
min_value: 10,
|
||||
max_value: 20,
|
||||
},
|
||||
];
|
||||
if (sql.includes('FROM skill_chip_secondary_options'))
|
||||
return secondaryStatIds.map((stat_attribute_id) => ({
|
||||
stat_attribute_id,
|
||||
}));
|
||||
if (sql.includes('FROM skill_chip_secondary_quality_values'))
|
||||
return secondaryStatIds.map((stat_attribute_id) => ({
|
||||
stat_attribute_id,
|
||||
quality: 'Superior',
|
||||
min_value: 1,
|
||||
max_value: 5,
|
||||
}));
|
||||
return [];
|
||||
}),
|
||||
run: mocks.insertBuild,
|
||||
}));
|
||||
});
|
||||
|
||||
it('rejects a selected Skill Chip assigned to a different focus ability', async () => {
|
||||
mocks.database.prepare.mockImplementation(async (sql: string) => ({
|
||||
all: vi.fn(async () => {
|
||||
if (sql.includes('FROM skill_chip_sets'))
|
||||
return [
|
||||
{
|
||||
id: 'chip-set-1',
|
||||
specialization_ability_id: 'ability-2',
|
||||
},
|
||||
];
|
||||
if (sql.includes('FROM specialization_focus_abilities'))
|
||||
return [{ ability_id: 'ability-1', ability_slot: 1 }];
|
||||
return [];
|
||||
}),
|
||||
run: mocks.insertBuild,
|
||||
}));
|
||||
|
||||
const result = await runSave(validLoadout());
|
||||
|
||||
expect(result).toMatchObject({
|
||||
status: 400,
|
||||
data: {
|
||||
buildError: 'Skill Chip 1 is not compatible with its assigned ability.',
|
||||
},
|
||||
});
|
||||
expect(mocks.insertBuild).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
label: 'primary',
|
||||
mutate: (loadout: LoadoutDraft) => {
|
||||
loadout.chips[0].primary.value = 21;
|
||||
},
|
||||
message: 'Skill Chip 1 has an invalid primary Attribute value.',
|
||||
},
|
||||
{
|
||||
label: 'secondary',
|
||||
mutate: (loadout: LoadoutDraft) => {
|
||||
loadout.chips[0].secondary[1].value = 6;
|
||||
},
|
||||
message: 'Skill Chip 1 has an invalid secondary Attribute value.',
|
||||
},
|
||||
])('rejects an out-of-range $label value', async ({ mutate, message }) => {
|
||||
const loadout = validLoadout();
|
||||
mutate(loadout);
|
||||
|
||||
const result = await runSave(loadout);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
status: 400,
|
||||
data: { buildError: message },
|
||||
});
|
||||
expect(mocks.insertBuild).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('rejects duplicate secondary Attributes', async () => {
|
||||
const loadout = validLoadout();
|
||||
loadout.chips[0].secondary[1].statId = secondaryStatIds[0];
|
||||
|
||||
const result = await runSave(loadout);
|
||||
|
||||
expect(result).toMatchObject({
|
||||
status: 400,
|
||||
data: {
|
||||
buildError: 'Skill Chip 1 must use distinct secondary Attributes.',
|
||||
},
|
||||
});
|
||||
expect(mocks.insertBuild).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('accepts primary and secondary values within the configured profiles', async () => {
|
||||
const result = await runSave(validLoadout());
|
||||
|
||||
expect(result).toMatchObject({
|
||||
buildSaved: true,
|
||||
buildToken: expect.any(String),
|
||||
savedBuild: {
|
||||
visibility: 'private',
|
||||
title: 'Validated Skill Chips',
|
||||
},
|
||||
});
|
||||
expect(mocks.insertBuild).toHaveBeenCalledOnce();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user