Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { provideZonelessChangeDetection } from '@angular/core';
import { provideNoopAnimations } from '@angular/platform-browser/animations';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { Conditions } from '@keira/shared/acore-world-model';
import { CONDITIONS_TABLE, Conditions } from '@keira/shared/acore-world-model';
import { MysqlQueryService, SqliteService } from '@keira/shared/db-layer';
import { EditorPageObject, TranslateTestingModule } from '@keira/shared/test-utils';
import { ModalModule } from 'ngx-bootstrap/modal';
import { ToastrModule } from 'ngx-toastr';
import { of } from 'rxjs';
import { of, throwError } from 'rxjs';
import { instance, mock } from 'ts-mockito';
import { ConditionsHandlerService } from '../conditions-handler.service';
import { ConditionsComponent } from './conditions.component';
Expand Down Expand Up @@ -106,8 +106,7 @@ describe('Conditions integration tests', () => {
expect(handlerService.isConditionsUnsaved()).toBe(false);
});

// TODO: fix this - broken with provideZonelessChangeDetection()
it.skip('changing a property and executing the query should correctly work', () => {
it('changing a property and executing the query should correctly work', async () => {
const { page, querySpy } = setup(true);
const expectedQuery =
'DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = 2) AND (`SourceGroup` = 3) AND ' +
Expand All @@ -116,13 +115,25 @@ describe('Conditions integration tests', () => {
') AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 0) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 0) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0);\n' +
'INSERT INTO `conditions` (`SourceTypeOrReferenceId`, `SourceGroup`, `SourceEntry`, `SourceId`, `ElseGroup`, `ConditionTypeOrReference`, `ConditionTarget`, `ConditionValue1`, `ConditionValue2`, `ConditionValue3`, `NegativeCondition`, `ErrorType`, `ErrorTextId`, `ScriptName`, `Comment`) VALUES\n' +
"(2, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '');";

// After save, the editor reloads the (now-existing) entity via its new composite key;
// make the reload return the persisted row so the post-save reload succeeds.
const savedEntity = new Conditions();
savedEntity.SourceTypeOrReferenceId = 2;
savedEntity.SourceGroup = 3;
savedEntity.SourceEntry = sourceEntry;
vi.spyOn(TestBed.inject(MysqlQueryService), 'selectAllMultipleKeys').mockReturnValue(of([savedEntity]));

querySpy.mockClear();

page.setSelectValueById('SourceTypeOrReferenceId', 2);
await page.whenReady();
page.setInputValueById('SourceGroup', 3);
await page.whenReady();
page.expectFullQueryToContain(expectedQuery);

page.clickExecuteQuery();
await page.whenReady();

expect(querySpy).toHaveBeenCalledTimes(1);
expect(querySpy.mock.calls.at(-1)[0]).toContain(expectedQuery);
Expand Down Expand Up @@ -180,6 +191,23 @@ describe('Conditions integration tests', () => {
sourceEntry +
') AND (`SourceId` = 0) AND (`ElseGroup` = 0) AND (`ConditionTypeOrReference` = 0) AND (`ConditionTarget` = 0) AND (`ConditionValue1` = 0) AND (`ConditionValue2` = 0) AND (`ConditionValue3` = 0)',
);
// Additional structured-matcher assertion (alongside the exact-string check above).
page.expectDiffQueryToUpdate(
CONDITIONS_TABLE,
{
SourceTypeOrReferenceId: sourceTypeOrReferenceId,
SourceGroup: sourceGroup,
SourceEntry: sourceEntry,
SourceId: 0,
ElseGroup: 0,
ConditionTypeOrReference: 0,
ConditionTarget: 0,
ConditionValue1: 0,
ConditionValue2: 0,
ConditionValue3: 0,
},
{ SourceGroup: 1 },
);
page.expectFullQueryToContain(
'DELETE FROM `conditions` WHERE (`SourceTypeOrReferenceId` = ' +
sourceTypeOrReferenceId +
Expand Down Expand Up @@ -214,5 +242,109 @@ describe('Conditions integration tests', () => {
"(1, 1, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '', '');",
);
});

it('schema sweep: every editable field flows into the diff query', async () => {
const { page, querySpy } = setup(false);
// Conditions has no computed/disabled fields, so the excluded list is empty.
const written = await page.changeAllFieldsAsync(originalEntity, []);

for (const field of Object.keys(written)) {
page.expectDiffQueryToContain(`\`${field}\` =`);
}

querySpy.mockClear();
page.clickExecuteQuery();
expect(querySpy).toHaveBeenCalledTimes(1);
});

it('shows an error toast when the save query fails', async () => {
const { page, querySpy } = setup(false);
page.setInputValueById('SourceGroup', '99'); // make the form dirty

querySpy.mockReturnValue(throwError(() => new Error('mock SQL failure')));
page.clickExecuteQuery();
await page.whenReady();

page.expectErrorToastVisible();
});
});

describe('Selectors', () => {
// NOTE: the selector-button DOM id is derived from the FlagsSelectorBtn `config.name`
// (-> `ConditionValue1`/`ConditionValue2`), not from the explicit host `id` attribute
// (`queststate-flag-selector` / `rankmask-flag-selector`). Because the @if arms are
// mutually exclusive on ConditionTypeOrReference, only one button renders at a time,
// so `ConditionValue2-selector-btn` resolves uniquely once the type is set.
it('TYPEMASK flags selector writes a bitmask to ConditionValue1', async () => {
const { page } = setup(false);
page.setSelectValueById('ConditionTypeOrReference', 32); // CONDITION_TYPE_MASK -> showTypeMask
await page.whenReady();

// TYPEMASK display rows 0 and 2 map to bits 3 (8) and 5 (32) -> 40
const value = await page.openFlagsAndToggle('ConditionValue1', [0, 2]);

expect(value).toBe(40);
page.expectDiffQueryToContain('`ConditionValue1` = 40');
});

it('QUEST_STATE flags selector writes to ConditionValue2', async () => {
const { page } = setup(false);
page.setSelectValueById('ConditionTypeOrReference', 47); // CONDITION_QUESTSTATE -> showQuestState
await page.whenReady();

// QUEST_STATE display row 1 maps to bit 1 -> value 2
const value = await page.openFlagsAndToggle('ConditionValue2', [1]);

expect(value).toBe(2);
page.expectDiffQueryToContain('`ConditionValue2` = 2');
});

it('RANKMASK flags selector writes to ConditionValue2', async () => {
const { page } = setup(false);
page.setSelectValueById('ConditionTypeOrReference', 5); // CONDITION_REPUTATION_RANK -> showReactionTo
await page.whenReady();

// RANKMASK display row 1 maps to bit 1 -> value 2
const value = await page.openFlagsAndToggle('ConditionValue2', [1]);

expect(value).toBe(2);
page.expectDiffQueryToContain('`ConditionValue2` = 2');
});

it('OBJECT_ENTRY_GUID single-value selector writes to ConditionValue1', async () => {
const { page } = setup(false);
page.setSelectValueById('ConditionTypeOrReference', 31); // CONDITION_OBJECT_ENTRY_GUID -> showObjectEntryGuid
await page.whenReady();

// CONDITION_OBJECT_ENTRY_GUID_CV1 row 0 -> value 3
const value = await page.openSelectorAndPickRow('ConditionValue1', 0);

expect(value).not.toBe('0');
page.expectDiffQueryToContain(`\`ConditionValue1\` = ${value}`);
});

it('LEVEL single-value selector writes to ConditionValue2', async () => {
const { page } = setup(false);
page.setSelectValueById('ConditionTypeOrReference', 27); // CONDITION_LEVEL -> showLevel
await page.whenReady();

// CONDITION_LEVEL_CV2 row 1 -> value 1 (row 0 is value 0 and would not produce a diff)
const value = await page.openSelectorAndPickRow('ConditionValue2', 1);

expect(value).not.toBe('0');
page.expectDiffQueryToContain(`\`ConditionValue2\` = ${value}`);
});

it('INSTANCE_INFO single-value selector writes to ConditionValue3', async () => {
const { page } = setup(false);
page.setSelectValueById('ConditionTypeOrReference', 13); // CONDITION_INSTANCE_INFO -> showInstanceInfo
await page.whenReady();

// CONDITION_INSTANCE_INFO_CV3 row 1 -> value 1 (row 0 is value 0 and would not produce a diff)
const value = await page.openSelectorAndPickRow('ConditionValue3', 1);

expect(value).not.toBe('0');
page.expectDiffQueryToContain(`\`ConditionValue3\` = ${value}`);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ describe('SelectConditions integration tests', () => {

function setup() {
const navigateSpy = vi.spyOn(TestBed.inject(Router), 'navigate').mockImplementation(() => undefined);
const handlerService = TestBed.inject(ConditionsHandlerService);
const queryService = TestBed.inject(MysqlQueryService);
const querySpy = vi.spyOn(queryService, 'query').mockReturnValue(of([{ max: 1 }]));

Expand All @@ -61,7 +62,7 @@ describe('SelectConditions integration tests', () => {
fixture.autoDetectChanges(true);
fixture.detectChanges();

return { fixture, page, querySpy, navigateSpy };
return { fixture, page, querySpy, navigateSpy, handlerService };
}

it('should correctly initialise', async () => {
Expand All @@ -70,6 +71,24 @@ describe('SelectConditions integration tests', () => {
expect(page.queryWrapper.innerText).toContain('SELECT * FROM `conditions` LIMIT 50');
});

it('clearing the limit input drops the LIMIT clause entirely', () => {
const { page, querySpy } = setup();
// default query carries LIMIT 50
expect(page.queryWrapper.innerText).toContain('SELECT * FROM `conditions` LIMIT 50');

querySpy.mockClear();
page.setInputValue(page.searchLimitInput, '');

const expectedQuery = 'SELECT * FROM `conditions`';
expect(page.queryWrapper.innerText).toContain(expectedQuery);
expect(page.queryWrapper.innerText).not.toContain('LIMIT');

page.clickElement(page.searchBtn);

expect(querySpy).toHaveBeenCalledTimes(1);
expect(querySpy).toHaveBeenCalledWith(expectedQuery);
});

for (const { testId, sourceIdorRef, group, entry, limit, expectedQuery } of [
{
testId: 1,
Expand Down Expand Up @@ -153,7 +172,8 @@ describe('SelectConditions integration tests', () => {
}

it('searching and selecting an existing entity from the datatable should correctly work', () => {
const { page, querySpy, navigateSpy } = setup();
const { page, querySpy, navigateSpy, handlerService } = setup();
const selectSpy = vi.spyOn(handlerService, 'select');
const results = [
{
SourceTypeOrReferenceId: 1,
Expand Down Expand Up @@ -223,6 +243,22 @@ describe('SelectConditions integration tests', () => {

page.clickElement(page.getDatatableCellExternal(1, 1));

expect(selectSpy).toHaveBeenCalledTimes(1);
expect(selectSpy).toHaveBeenCalledWith(
false,
expect.objectContaining({
SourceTypeOrReferenceId: results[1].SourceTypeOrReferenceId,
SourceGroup: results[1].SourceGroup,
SourceEntry: results[1].SourceEntry,
SourceId: results[1].SourceId,
ElseGroup: results[1].ElseGroup,
ConditionTypeOrReference: results[1].ConditionTypeOrReference,
ConditionTarget: results[1].ConditionTarget,
ConditionValue1: results[1].ConditionValue1,
ConditionValue2: results[1].ConditionValue2,
ConditionValue3: results[1].ConditionValue3,
}),
);
expect(navigateSpy).toHaveBeenCalledTimes(1);
expect(navigateSpy).toHaveBeenCalledWith(['conditions/conditions']);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { MysqlQueryService, SqliteService } from '@keira/shared/db-layer';
import { MultiRowEditorPageObject, TranslateTestingModule } from '@keira/shared/test-utils';
import { ModalModule } from 'ngx-bootstrap/modal';
import { ToastrModule } from 'ngx-toastr';
import { of } from 'rxjs';
import { of, throwError } from 'rxjs';
import { instance, mock } from 'ts-mockito';
import { CreatureHandlerService } from '../creature-handler.service';
import { SaiCreatureHandlerService } from '../sai-creature-handler.service';
Expand Down Expand Up @@ -379,5 +379,27 @@ describe('CreatureFormations integration tests', () => {

page.expectUniqueError();
});

it('schema sweep: every editable field flows into the diff query', async () => {
const { page } = setup(false);
page.clickRowOfDatatable(0);
const written = await page.changeAllFieldsAsync(new CreatureFormation(), ['leaderGUID', 'memberGUID']);

for (const field of Object.keys(written)) {
page.expectDiffQueryToContain('`' + field + '`');
}
});

it('shows an error toast when the save query fails', async () => {
const { querySpy, page } = setup(false);
page.clickRowOfDatatable(0);
page.setInputValueById('dist', '7');

querySpy.mockReturnValue(throwError(() => new Error('mock SQL failure')));
page.clickExecuteQuery();
await page.whenReady();

page.expectErrorToastVisible();
});
});
});
Loading
Loading