diff --git a/apps/keira/src/app/routes.ts b/apps/keira/src/app/routes.ts index 04e5ec9e3b..3a93976f10 100644 --- a/apps/keira/src/app/routes.ts +++ b/apps/keira/src/app/routes.ts @@ -88,6 +88,9 @@ import { SelectBroadcastTextComponent, SelectNpcTextComponent, SelectPageTextComponent, + AcoreStringComponent, + AcoreStringHandlerService, + SelectAcoreStringComponent, } from 'texts'; export const KEIRA_ROUTES: Routes = [ @@ -406,6 +409,15 @@ export const KEIRA_ROUTES: Routes = [ component: NpcTextComponent, canActivate: [NpcTextHandlerService], }, + { + path: 'acore-string', + component: AcoreStringComponent, + canActivate: [AcoreStringHandlerService], + }, + { + path: 'select-acore-string', + component: SelectAcoreStringComponent, + }, ], }, { diff --git a/apps/keira/src/assets/i18n/de.json b/apps/keira/src/assets/i18n/de.json index 5016593d57..8195cbadd6 100644 --- a/apps/keira/src/assets/i18n/de.json +++ b/apps/keira/src/assets/i18n/de.json @@ -147,7 +147,9 @@ "SELECT_NPC_TEXT": "NPC-Text auswählen", "NPC_TEXT": "NPC-Text", "SELECT_PAGE_TEXT": "Seiten-Text auswählen", - "PAGE_TEXT": "Seiten-Text" + "PAGE_TEXT": "Seiten-Text", + "ACORE_STRING_TEXT": "Acore-String Texts", + "SELECT_ACORE_STRING_TEXT": "Acore-String Texts auswählen" }, "GOSSIP": { "TITLE": "Klatsch", diff --git a/apps/keira/src/assets/i18n/el.json b/apps/keira/src/assets/i18n/el.json index 14487cafcb..c41afdd39f 100644 --- a/apps/keira/src/assets/i18n/el.json +++ b/apps/keira/src/assets/i18n/el.json @@ -148,7 +148,9 @@ "SELECT_NPC_TEXT": "Select NPC Text", "NPC_TEXT": "NPC Text", "SELECT_PAGE_TEXT": "Select Page Text", - "PAGE_TEXT": "Page Text" + "PAGE_TEXT": "Page Text", + "ACORE_STRING_TEXT": "Acore Strings", + "SELECT_ACORE_STRING_TEXT": "Select Acore Strings" }, "GOSSIP": { "TITLE": "Gossip", diff --git a/apps/keira/src/assets/i18n/en.json b/apps/keira/src/assets/i18n/en.json index a92b6cfb2a..ff56377d43 100644 --- a/apps/keira/src/assets/i18n/en.json +++ b/apps/keira/src/assets/i18n/en.json @@ -148,7 +148,9 @@ "SELECT_NPC_TEXT": "Select NPC Text", "NPC_TEXT": "NPC Text", "SELECT_PAGE_TEXT": "Select Page Text", - "PAGE_TEXT": "Page Text" + "PAGE_TEXT": "Page Text", + "ACORE_STRING_TEXT": "Acore Strings", + "SELECT_ACORE_STRING_TEXT": "Select Acore Strings" }, "GOSSIP": { "TITLE": "Gossip", diff --git a/apps/keira/src/assets/i18n/es.json b/apps/keira/src/assets/i18n/es.json index 4b3265248a..f7e726b039 100644 --- a/apps/keira/src/assets/i18n/es.json +++ b/apps/keira/src/assets/i18n/es.json @@ -149,7 +149,9 @@ "ID": "The ID of the text in the page. This number is unique to every text ID.", "NEXT_PAGE_ID": "The ID of the next page's text ID.", "TEXT": "The actual text. The message in this field will be shown as the text on a page." - } + }, + "ACORE_STRING_TEXT": "Acore Strings", + "SELECT_ACORE_STRING_TEXT": "Select Acore Strings" }, "GOSSIP": { "TITLE": "Ventana de Texto de Criatura", diff --git a/apps/keira/src/assets/i18n/fr.json b/apps/keira/src/assets/i18n/fr.json index 918e1af96f..43c7ecf843 100644 --- a/apps/keira/src/assets/i18n/fr.json +++ b/apps/keira/src/assets/i18n/fr.json @@ -144,7 +144,9 @@ "SELECT_NPC_TEXT": "Select NPC Text", "NPC_TEXT": "NPC Text", "SELECT_PAGE_TEXT": "Select Page Text", - "PAGE_TEXT": "Page Text" + "PAGE_TEXT": "Page Text", + "ACORE_STRING_TEXT": "Acore Strings", + "SELECT_ACORE_STRING_TEXT": "Select Acore Strings" }, "GOSSIP": { "TITLE": "Gossip", diff --git a/apps/keira/src/assets/i18n/it.json b/apps/keira/src/assets/i18n/it.json index 08eef2e48b..2e14240293 100644 --- a/apps/keira/src/assets/i18n/it.json +++ b/apps/keira/src/assets/i18n/it.json @@ -147,7 +147,9 @@ "SELECT_NPC_TEXT": "Select NPC Text", "NPC_TEXT": "NPC Text", "SELECT_PAGE_TEXT": "Select Page Text", - "PAGE_TEXT": "Page Text" + "PAGE_TEXT": "Page Text", + "ACORE_STRING_TEXT": "Acore Strings", + "SELECT_ACORE_STRING_TEXT": "Select Acore Strings" }, "GOSSIP": { "TITLE": "Gossip", diff --git a/apps/keira/src/assets/i18n/ko.json b/apps/keira/src/assets/i18n/ko.json index 14487cafcb..c41afdd39f 100644 --- a/apps/keira/src/assets/i18n/ko.json +++ b/apps/keira/src/assets/i18n/ko.json @@ -148,7 +148,9 @@ "SELECT_NPC_TEXT": "Select NPC Text", "NPC_TEXT": "NPC Text", "SELECT_PAGE_TEXT": "Select Page Text", - "PAGE_TEXT": "Page Text" + "PAGE_TEXT": "Page Text", + "ACORE_STRING_TEXT": "Acore Strings", + "SELECT_ACORE_STRING_TEXT": "Select Acore Strings" }, "GOSSIP": { "TITLE": "Gossip", diff --git a/apps/keira/src/assets/i18n/nl.json b/apps/keira/src/assets/i18n/nl.json index 14487cafcb..c41afdd39f 100644 --- a/apps/keira/src/assets/i18n/nl.json +++ b/apps/keira/src/assets/i18n/nl.json @@ -148,7 +148,9 @@ "SELECT_NPC_TEXT": "Select NPC Text", "NPC_TEXT": "NPC Text", "SELECT_PAGE_TEXT": "Select Page Text", - "PAGE_TEXT": "Page Text" + "PAGE_TEXT": "Page Text", + "ACORE_STRING_TEXT": "Acore Strings", + "SELECT_ACORE_STRING_TEXT": "Select Acore Strings" }, "GOSSIP": { "TITLE": "Gossip", diff --git a/apps/keira/src/assets/i18n/pl.json b/apps/keira/src/assets/i18n/pl.json index 14487cafcb..c41afdd39f 100644 --- a/apps/keira/src/assets/i18n/pl.json +++ b/apps/keira/src/assets/i18n/pl.json @@ -148,7 +148,9 @@ "SELECT_NPC_TEXT": "Select NPC Text", "NPC_TEXT": "NPC Text", "SELECT_PAGE_TEXT": "Select Page Text", - "PAGE_TEXT": "Page Text" + "PAGE_TEXT": "Page Text", + "ACORE_STRING_TEXT": "Acore Strings", + "SELECT_ACORE_STRING_TEXT": "Select Acore Strings" }, "GOSSIP": { "TITLE": "Gossip", diff --git a/apps/keira/src/assets/i18n/pt.json b/apps/keira/src/assets/i18n/pt.json index 4c63b31e22..b0bc7e616d 100644 --- a/apps/keira/src/assets/i18n/pt.json +++ b/apps/keira/src/assets/i18n/pt.json @@ -144,7 +144,9 @@ "SELECT_NPC_TEXT": "Select NPC Text", "NPC_TEXT": "NPC Text", "SELECT_PAGE_TEXT": "Select Page Text", - "PAGE_TEXT": "Page Text" + "PAGE_TEXT": "Page Text", + "ACORE_STRING_TEXT": "Acore Strings", + "SELECT_ACORE_STRING_TEXT": "Select Acore Strings" }, "GOSSIP": { "TITLE": "Gossip", diff --git a/apps/keira/src/assets/i18n/ro.json b/apps/keira/src/assets/i18n/ro.json index 14487cafcb..c41afdd39f 100644 --- a/apps/keira/src/assets/i18n/ro.json +++ b/apps/keira/src/assets/i18n/ro.json @@ -148,7 +148,9 @@ "SELECT_NPC_TEXT": "Select NPC Text", "NPC_TEXT": "NPC Text", "SELECT_PAGE_TEXT": "Select Page Text", - "PAGE_TEXT": "Page Text" + "PAGE_TEXT": "Page Text", + "ACORE_STRING_TEXT": "Acore Strings", + "SELECT_ACORE_STRING_TEXT": "Select Acore Strings" }, "GOSSIP": { "TITLE": "Gossip", diff --git a/apps/keira/src/assets/i18n/ru.json b/apps/keira/src/assets/i18n/ru.json index 673a8b08fe..eb74491740 100644 --- a/apps/keira/src/assets/i18n/ru.json +++ b/apps/keira/src/assets/i18n/ru.json @@ -144,7 +144,9 @@ "SELECT_NPC_TEXT": "Select NPC Text", "NPC_TEXT": "NPC Text", "SELECT_PAGE_TEXT": "Select Page Text", - "PAGE_TEXT": "Page Text" + "PAGE_TEXT": "Page Text", + "ACORE_STRING_TEXT": "Acore Strings", + "SELECT_ACORE_STRING_TEXT": "Select Acore Strings" }, "GOSSIP": { "TITLE": "Разговоры", diff --git a/apps/keira/src/assets/i18n/sk.json b/apps/keira/src/assets/i18n/sk.json index 14487cafcb..c41afdd39f 100644 --- a/apps/keira/src/assets/i18n/sk.json +++ b/apps/keira/src/assets/i18n/sk.json @@ -148,7 +148,9 @@ "SELECT_NPC_TEXT": "Select NPC Text", "NPC_TEXT": "NPC Text", "SELECT_PAGE_TEXT": "Select Page Text", - "PAGE_TEXT": "Page Text" + "PAGE_TEXT": "Page Text", + "ACORE_STRING_TEXT": "Acore Strings", + "SELECT_ACORE_STRING_TEXT": "Select Acore Strings" }, "GOSSIP": { "TITLE": "Gossip", diff --git a/apps/keira/src/assets/i18n/sv.json b/apps/keira/src/assets/i18n/sv.json index c29971099b..17544e8f84 100644 --- a/apps/keira/src/assets/i18n/sv.json +++ b/apps/keira/src/assets/i18n/sv.json @@ -144,7 +144,9 @@ "SELECT_NPC_TEXT": "Select NPC Text", "NPC_TEXT": "NPC Text", "SELECT_PAGE_TEXT": "Select Page Text", - "PAGE_TEXT": "Page Text" + "PAGE_TEXT": "Page Text", + "ACORE_STRING_TEXT": "Acore Strings", + "SELECT_ACORE_STRING_TEXT": "Select Acore Strings" }, "GOSSIP": { "TITLE": "Gossip", diff --git a/apps/keira/src/assets/i18n/zh.json b/apps/keira/src/assets/i18n/zh.json index bd3160e77e..d413c4c4da 100644 --- a/apps/keira/src/assets/i18n/zh.json +++ b/apps/keira/src/assets/i18n/zh.json @@ -144,7 +144,9 @@ "SELECT_NPC_TEXT": "Select NPC Text", "NPC_TEXT": "NPC Text", "SELECT_PAGE_TEXT": "Select Page Text", - "PAGE_TEXT": "Page Text" + "PAGE_TEXT": "Page Text", + "ACORE_STRING_TEXT": "Acore Strings", + "SELECT_ACORE_STRING_TEXT": "Select Acore Strings" }, "GOSSIP": { "TITLE": "闲聊对话", diff --git a/libs/features/texts/src/acore-string/acore-string-handler.service.spec.ts b/libs/features/texts/src/acore-string/acore-string-handler.service.spec.ts new file mode 100644 index 0000000000..7547cdc8cc --- /dev/null +++ b/libs/features/texts/src/acore-string/acore-string-handler.service.spec.ts @@ -0,0 +1,27 @@ +import { TestBed } from '@angular/core/testing'; +import { AcoreStringHandlerService } from './acore-string-handler.service'; +import { ACORE_STRING_TABLE } from '@keira/shared/acore-world-model'; + +describe(AcoreStringHandlerService.name, () => { + beforeEach(() => + TestBed.configureTestingModule({ + providers: [AcoreStringHandlerService], + }), + ); + + const setup = () => { + const service = TestBed.inject(AcoreStringHandlerService); + return { service }; + }; + + it('isUnsaved should return the value of the statusMap for the broadcast_text table', () => { + const { service } = setup(); + expect(service.isUnsaved).toBe(false); // defaults to false + + service.statusMap[ACORE_STRING_TABLE] = true; + expect(service.isUnsaved).toBe(true); + + service.statusMap[ACORE_STRING_TABLE] = false; + expect(service.isUnsaved).toBe(false); + }); +}); diff --git a/libs/features/texts/src/acore-string/acore-string-handler.service.ts b/libs/features/texts/src/acore-string/acore-string-handler.service.ts new file mode 100644 index 0000000000..449cd10376 --- /dev/null +++ b/libs/features/texts/src/acore-string/acore-string-handler.service.ts @@ -0,0 +1,18 @@ +import { Injectable } from '@angular/core'; +import { HandlerService } from '@keira/shared/base-abstract-classes'; +import { ACORE_STRING_TABLE, AcoreString } from '@keira/shared/acore-world-model'; + +@Injectable({ + providedIn: 'root', +}) +export class AcoreStringHandlerService extends HandlerService { + protected readonly mainEditorRoutePath = 'texts/acore-string'; + + get isUnsaved(): boolean { + return this.statusMap[ACORE_STRING_TABLE]; + } + + protected _statusMap = { + [ACORE_STRING_TABLE]: false, + }; +} diff --git a/libs/features/texts/src/acore-string/acore-string.component.html b/libs/features/texts/src/acore-string/acore-string.component.html new file mode 100644 index 0000000000..a6497231ef --- /dev/null +++ b/libs/features/texts/src/acore-string/acore-string.component.html @@ -0,0 +1,73 @@ + + +
+ @if (editorService.loading) { + + } + + @if (editorService.form && !!editorService.loadedEntityId && !editorService.loading) { +
+
+ +
+
+
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+ + +
+
+
+
+
+ } +
diff --git a/libs/features/texts/src/acore-string/acore-string.component.ts b/libs/features/texts/src/acore-string/acore-string.component.ts new file mode 100644 index 0000000000..77edf02a25 --- /dev/null +++ b/libs/features/texts/src/acore-string/acore-string.component.ts @@ -0,0 +1,20 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { ReactiveFormsModule } from '@angular/forms'; +import { AcoreString } from '@keira/shared/acore-world-model'; +import { SingleRowEditorComponent } from '@keira/shared/base-abstract-classes'; +import { TranslateModule } from '@ngx-translate/core'; +import { TooltipModule } from 'ngx-bootstrap/tooltip'; +import { AcoreStringHandlerService } from './acore-string-handler.service'; +import { AcoreStringService } from './acore-string.service'; +import { QueryOutputComponent, TopBarComponent } from '@keira/shared/base-editor-components'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './acore-string.component.html', + standalone: true, + imports: [TranslateModule, ReactiveFormsModule, TooltipModule, QueryOutputComponent, TopBarComponent], +}) +export class AcoreStringComponent extends SingleRowEditorComponent { + override readonly editorService = inject(AcoreStringService); + protected override readonly handlerService = inject(AcoreStringHandlerService); +} diff --git a/libs/features/texts/src/acore-string/acore-string.integration.spec.ts b/libs/features/texts/src/acore-string/acore-string.integration.spec.ts new file mode 100644 index 0000000000..55f8f2cbca --- /dev/null +++ b/libs/features/texts/src/acore-string/acore-string.integration.spec.ts @@ -0,0 +1,158 @@ +import { TestBed, waitForAsync } from '@angular/core/testing'; +import { MysqlQueryService } from '@keira/shared/db-layer'; +import { EditorPageObject, TranslateTestingModule } from '@keira/shared/test-utils'; +import { AcoreString } from '@keira/shared/acore-world-model'; +import { ModalModule } from 'ngx-bootstrap/modal'; +import { ToastrModule } from 'ngx-toastr'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { of } from 'rxjs'; +import { KEIRA_APP_CONFIG_TOKEN, KEIRA_MOCK_CONFIG } from '@keira/shared/config'; +import { AcoreStringComponent } from './acore-string.component'; +import { AcoreStringHandlerService } from './acore-string-handler.service'; + +describe('Acore String integration tests', () => { + class Page extends EditorPageObject {} + + const entry = 1234; + const expectedFullCreateQuery = + 'DELETE FROM `acore_string` WHERE (`entry` = 1234);\n' + + 'INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, ' + + '`locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`)' + + ' VALUES\n' + + "(1234, '', '', '', '', '', '', '', '', '');"; + + const originalEntity = new AcoreString(); + originalEntity.entry = entry; + originalEntity.content_default = 'Hi'; + originalEntity.locale_koKR = '1'; + originalEntity.locale_frFR = '2'; + originalEntity.locale_deDE = '3'; + originalEntity.locale_zhCN = '4'; + originalEntity.locale_zhTW = '5'; + originalEntity.locale_esES = '6'; + originalEntity.locale_esMX = '7'; + originalEntity.locale_ruRU = '8'; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [BrowserAnimationsModule, ToastrModule.forRoot(), ModalModule.forRoot(), AcoreStringComponent, TranslateTestingModule], + providers: [{ provide: KEIRA_APP_CONFIG_TOKEN, useValue: KEIRA_MOCK_CONFIG }], + }).compileComponents(); + })); + + function setup(creatingNew: boolean) { + const handlerService = TestBed.inject(AcoreStringHandlerService); + handlerService['_selected'] = `${entry}`; + handlerService.isNew = creatingNew; + + const queryService = TestBed.inject(MysqlQueryService); + const querySpy = spyOn(queryService, 'query').and.returnValue(of([])); + + spyOn(queryService, 'selectAll').and.returnValue(of(creatingNew ? [] : [originalEntity])); + + const fixture = TestBed.createComponent(AcoreStringComponent); + const component = fixture.componentInstance; + const page = new Page(fixture); + fixture.autoDetectChanges(true); + fixture.detectChanges(); + + return { originalEntity, handlerService, queryService, querySpy, fixture, component, page }; + } + + describe('Creating new', () => { + it('should correctly initialise', () => { + const { page } = setup(true); + page.expectQuerySwitchToBeHidden(); + page.expectFullQueryToBeShown(); + page.expectFullQueryToContain(expectedFullCreateQuery); + page.removeNativeElement(); + }); + + it('should correctly update the unsaved status', () => { + const { page, handlerService } = setup(true); + const field = 'content_default'; + expect(handlerService.isUnsaved).toBe(false); + page.setInputValueById(field, 'Hi, i am a quest'); + expect(handlerService.isUnsaved).toBe(true); + page.setInputValueById(field, ''); + expect(handlerService.isUnsaved).toBe(false); + page.removeNativeElement(); + }); + + it('changing a property and executing the query should correctly work', () => { + const { page, querySpy } = setup(true); + const expectedQuery = + 'DELETE FROM `acore_string` WHERE (`entry` = 1234);\n' + + 'INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, ' + + '`locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`)' + + ' VALUES\n' + + "(1234, 'Hi', '', '', '', '', '', '', '', '');"; + querySpy.calls.reset(); + + page.setInputValueById('content_default', 'Hi'); + page.expectFullQueryToContain(expectedQuery); + + page.clickExecuteQuery(); + + expect(querySpy).toHaveBeenCalledTimes(1); + expect(querySpy.calls.mostRecent().args[0]).toContain(expectedQuery); + page.removeNativeElement(); + }); + }); + + describe('Editing existing', () => { + it('should correctly initialise', () => { + const { page } = setup(false); + page.expectDiffQueryToBeShown(); + page.expectDiffQueryToBeEmpty(); + page.expectFullQueryToContain( + 'DELETE FROM `acore_string` WHERE (`entry` = 1234);\n' + + 'INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, ' + + '`locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`)' + + ' VALUES\n' + + "(1234, 'Hi', '1', '2', '3', '4', '5', '6', '7', '8');", + ); + page.removeNativeElement(); + }); + + it('changing all properties and executing the query should correctly work', () => { + const { page, querySpy } = setup(false); + const expectedQuery = "UPDATE `acore_string` SET `content_default` = '0' WHERE (`entry` = 1234);"; + querySpy.calls.reset(); + + page.changeAllFields(originalEntity, ['entry']); + page.expectDiffQueryToContain(expectedQuery); + + page.clickExecuteQuery(); + expect(querySpy).toHaveBeenCalledTimes(1); + expect(querySpy.calls.mostRecent().args[0]).toContain(expectedQuery); + page.removeNativeElement(); + }); + + it('changing values should correctly update the queries', () => { + const { page } = setup(false); + page.setInputValueById('content_default', 'Hello'); + page.expectDiffQueryToContain("UPDATE `acore_string` SET `content_default` = 'Hello' WHERE (`entry` = 1234);"); + page.expectFullQueryToContain( + 'DELETE FROM `acore_string` WHERE (`entry` = 1234);\n' + + 'INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, ' + + '`locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`)' + + ' VALUES\n' + + "(1234, 'Hello', '1', '2', '3', '4', '5', '6', '7', '8');", + ); + + page.setInputValueById('locale_deDE', 'Hallo'); + page.expectDiffQueryToContain( + "UPDATE `acore_string` SET `content_default` = 'Hello', `locale_deDE` = 'Hallo' WHERE (`entry` = 1234);", + ); + page.expectFullQueryToContain( + 'DELETE FROM `acore_string` WHERE (`entry` = 1234);\n' + + 'INSERT INTO `acore_string` (`entry`, `content_default`, `locale_koKR`, `locale_frFR`, `locale_deDE`, ' + + '`locale_zhCN`, `locale_zhTW`, `locale_esES`, `locale_esMX`, `locale_ruRU`)' + + ' VALUES\n' + + "(1234, 'Hello', '1', '2', 'Hallo', '4', '5', '6', '7', '8');", + ); + page.removeNativeElement(); + }); + }); +}); diff --git a/libs/features/texts/src/acore-string/acore-string.service.spec.ts b/libs/features/texts/src/acore-string/acore-string.service.spec.ts new file mode 100644 index 0000000000..b8632f9269 --- /dev/null +++ b/libs/features/texts/src/acore-string/acore-string.service.spec.ts @@ -0,0 +1,17 @@ +import { TestBed } from '@angular/core/testing'; +import { AcoreStringService } from './acore-string.service'; +import { AcoreStringHandlerService } from './acore-string-handler.service'; +import { instance, mock } from 'ts-mockito'; +import { ToastrService } from 'ngx-toastr'; + +describe(AcoreStringService.name, () => { + beforeEach(() => + TestBed.configureTestingModule({ + providers: [AcoreStringHandlerService, { provide: ToastrService, useValue: instance(mock(ToastrService)) }], + }), + ); + + it('should be created', () => { + expect(TestBed.inject(AcoreStringService)).toBeTruthy(); + }); +}); diff --git a/libs/features/texts/src/acore-string/acore-string.service.ts b/libs/features/texts/src/acore-string/acore-string.service.ts new file mode 100644 index 0000000000..e2cef4b0cd --- /dev/null +++ b/libs/features/texts/src/acore-string/acore-string.service.ts @@ -0,0 +1,14 @@ +import { Injectable } from '@angular/core'; +import { SingleRowEditorService } from '@keira/shared/base-abstract-classes'; +import { AcoreStringHandlerService } from './acore-string-handler.service'; +import { ACORE_STRING_DEFAULT, ACORE_STRING_ENTRY, ACORE_STRING_TABLE, AcoreString } from '@keira/shared/acore-world-model'; + +@Injectable({ + providedIn: 'root', +}) +export class AcoreStringService extends SingleRowEditorService { + /* istanbul ignore next */ // because of: https://github.com/gotwarlost/istanbul/issues/690 + constructor(protected override handlerService: AcoreStringHandlerService) { + super(AcoreString, ACORE_STRING_TABLE, ACORE_STRING_ENTRY, ACORE_STRING_DEFAULT, true, handlerService); + } +} diff --git a/libs/features/texts/src/acore-string/select-acore-string.component.html b/libs/features/texts/src/acore-string/select-acore-string.component.html new file mode 100644 index 0000000000..d6bfd767bd --- /dev/null +++ b/libs/features/texts/src/acore-string/select-acore-string.component.html @@ -0,0 +1,62 @@ + + +
+
+ +
+ +
+

+
+
+ +
+ +
+
+ +
+
+
+ +
+
+ +
+
+ + + + @if (selectService.rows) { +
+ + + + +
+ } +
+
diff --git a/libs/features/texts/src/acore-string/select-acore-string.component.ts b/libs/features/texts/src/acore-string/select-acore-string.component.ts new file mode 100644 index 0000000000..c0d1865892 --- /dev/null +++ b/libs/features/texts/src/acore-string/select-acore-string.component.ts @@ -0,0 +1,31 @@ +import { ChangeDetectionStrategy, Component, inject } from '@angular/core'; +import { SelectComponent } from '@keira/shared/base-abstract-classes'; +import { ACORE_STRING_CUSTOM_STARTING_ID, ACORE_STRING_ENTRY, ACORE_STRING_TABLE, AcoreString } from '@keira/shared/acore-world-model'; +import { NgxDatatableModule } from '@siemens/ngx-datatable'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '@ngx-translate/core'; +import { SelectAcoreStringService } from './select-acore-string.service'; +import { AcoreStringHandlerService } from './acore-string-handler.service'; +import { CreateComponent, HighlightjsWrapperComponent, TopBarComponent } from '@keira/shared/base-editor-components'; + +@Component({ + changeDetection: ChangeDetectionStrategy.OnPush, + templateUrl: './select-acore-string.component.html', + standalone: true, + imports: [ + TranslateModule, + FormsModule, + ReactiveFormsModule, + NgxDatatableModule, + TopBarComponent, + CreateComponent, + HighlightjsWrapperComponent, + ], +}) +export class SelectAcoreStringComponent extends SelectComponent { + readonly entityTable = ACORE_STRING_TABLE; + readonly entityIdField = ACORE_STRING_ENTRY; + readonly customStartingId = ACORE_STRING_CUSTOM_STARTING_ID; + readonly selectService = inject(SelectAcoreStringService); + readonly handlerService = inject(AcoreStringHandlerService); +} diff --git a/libs/features/texts/src/acore-string/select-acore-string.integration.spec.ts b/libs/features/texts/src/acore-string/select-acore-string.integration.spec.ts new file mode 100644 index 0000000000..f1143b6d53 --- /dev/null +++ b/libs/features/texts/src/acore-string/select-acore-string.integration.spec.ts @@ -0,0 +1,137 @@ +import { TestBed, waitForAsync } from '@angular/core/testing'; +import { Router } from '@angular/router'; +import { MysqlQueryService } from '@keira/shared/db-layer'; +import { SelectPageObject, TranslateTestingModule } from '@keira/shared/test-utils'; +import { ACORE_STRING_ENTRY } from '@keira/shared/acore-world-model'; +import { ModalModule } from 'ngx-bootstrap/modal'; +import { ToastrModule } from 'ngx-toastr'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { of } from 'rxjs'; +import { SelectAcoreStringComponent } from './select-acore-string.component'; +import { AcoreStringHandlerService } from './acore-string-handler.service'; +import { SelectAcoreStringService } from './select-acore-string.service'; + +describe(`${SelectAcoreStringComponent.name} integration tests`, () => { + class Page extends SelectPageObject { + override ID_FIELD = ACORE_STRING_ENTRY; + } + + const value = 1200; + const expectedRoute = 'texts/acore-string'; + + beforeEach(waitForAsync(() => { + TestBed.configureTestingModule({ + imports: [BrowserAnimationsModule, ToastrModule.forRoot(), ModalModule.forRoot(), SelectAcoreStringComponent, TranslateTestingModule], + providers: [AcoreStringHandlerService], + }).compileComponents(); + })); + + function setup() { + const navigateSpy = spyOn(TestBed.inject(Router), 'navigate'); + const queryService = TestBed.inject(MysqlQueryService); + const querySpy = spyOn(queryService, 'query').and.returnValue(of([{ max: 1 }])); + + const selectService = TestBed.inject(SelectAcoreStringService); + + const fixture = TestBed.createComponent(SelectAcoreStringComponent); + const page = new Page(fixture); + const component = fixture.componentInstance; + fixture.autoDetectChanges(true); + fixture.detectChanges(); + + return { component, fixture, selectService, acoreStrings: page, queryService, querySpy, navigateSpy }; + } + + it('should correctly initialise', waitForAsync(async () => { + const { fixture, acoreStrings, querySpy, component } = setup(); + + await fixture.whenStable(); + expect(acoreStrings.createInput.value).toEqual(`${component.customStartingId}`); + acoreStrings.expectNewEntityFree(); + expect(querySpy).toHaveBeenCalledWith('SELECT MAX(entry) AS max FROM acore_string;'); + expect(acoreStrings.queryWrapper.innerText).toContain('SELECT * FROM `acore_string` LIMIT 50'); + })); + + it('should correctly behave when inserting and selecting free entry', waitForAsync(async () => { + const { fixture, acoreStrings, querySpy, navigateSpy } = setup(); + + await fixture.whenStable(); + querySpy.calls.reset(); + querySpy.and.returnValue(of([])); + + acoreStrings.setInputValue(acoreStrings.createInput, value); + + expect(querySpy).toHaveBeenCalledTimes(1); + expect(querySpy).toHaveBeenCalledWith(`SELECT * FROM \`acore_string\` WHERE (entry = ${value})`); + acoreStrings.expectNewEntityFree(); + + acoreStrings.clickElement(acoreStrings.selectNewBtn); + + expect(navigateSpy).toHaveBeenCalledTimes(1); + expect(navigateSpy).toHaveBeenCalledWith([expectedRoute]); + acoreStrings.expectTopBarCreatingNew(value); + })); + + it('should correctly behave when inserting an existing entity', waitForAsync(async () => { + const { fixture, acoreStrings, querySpy } = setup(); + + await fixture.whenStable(); + querySpy.calls.reset(); + querySpy.and.returnValue(of([{}])); + + acoreStrings.setInputValue(acoreStrings.createInput, value); + + expect(querySpy).toHaveBeenCalledTimes(1); + expect(querySpy).toHaveBeenCalledWith(`SELECT * FROM \`acore_string\` WHERE (entry = ${value})`); + acoreStrings.expectEntityAlreadyInUse(); + })); + + for (const { id, entry, limit, expectedQuery } of [ + { + id: 1, + entry: 1200, + limit: '100', + expectedQuery: 'SELECT * FROM `acore_string` ' + "WHERE (`entry` LIKE '%1200%') LIMIT 100", + }, + ]) { + it(`searching an existing entity should correctly work [${id}]`, () => { + const { acoreStrings, querySpy } = setup(); + + querySpy.calls.reset(); + if (entry) { + acoreStrings.setInputValue(acoreStrings.searchIdInput, entry); + } + acoreStrings.setInputValue(acoreStrings.searchLimitInput, limit); + + expect(acoreStrings.queryWrapper.innerText).toContain(expectedQuery); + + acoreStrings.clickElement(acoreStrings.searchBtn); + + expect(querySpy).toHaveBeenCalledTimes(1); + expect(querySpy).toHaveBeenCalledWith(expectedQuery); + }); + } + + it('searching and selecting an existing entity from the datatable should correctly work', () => { + const { navigateSpy, acoreStrings, querySpy } = setup(); + + const results = [{ entry: 1 }, { entry: 2 }, { entry: 3 }]; + querySpy.calls.reset(); + querySpy.and.returnValue(of(results)); + + acoreStrings.clickElement(acoreStrings.searchBtn); + + const row0 = acoreStrings.getDatatableRow(0); + const row1 = acoreStrings.getDatatableRow(1); + const row2 = acoreStrings.getDatatableRow(2); + + expect(row0.innerText).toContain(String(results[0].entry)); + expect(row1.innerText).toContain(String(results[1].entry)); + expect(row2.innerText).toContain(String(results[2].entry)); + + acoreStrings.clickElement(acoreStrings.getDatatableCell(0, 0)); + + expect(navigateSpy).toHaveBeenCalledTimes(1); + expect(navigateSpy).toHaveBeenCalledWith([expectedRoute]); + }); +}); diff --git a/libs/features/texts/src/acore-string/select-acore-string.service.spec.ts b/libs/features/texts/src/acore-string/select-acore-string.service.spec.ts new file mode 100644 index 0000000000..dbf98e05d2 --- /dev/null +++ b/libs/features/texts/src/acore-string/select-acore-string.service.spec.ts @@ -0,0 +1,22 @@ +import { TestBed } from '@angular/core/testing'; +import { MysqlQueryService } from '@keira/shared/db-layer'; + +import { instance, mock } from 'ts-mockito'; +import { SelectAcoreStringService } from './select-acore-string.service'; +import { AcoreStringHandlerService } from './acore-string-handler.service'; + +describe(SelectAcoreStringService.name, () => { + beforeEach(() => + TestBed.configureTestingModule({ + providers: [ + { provide: MysqlQueryService, useValue: instance(mock(MysqlQueryService)) }, + SelectAcoreStringService, + AcoreStringHandlerService, + ], + }), + ); + + it('should be created', () => { + expect(TestBed.inject(SelectAcoreStringService)).toBeTruthy(); + }); +}); diff --git a/libs/features/texts/src/acore-string/select-acore-string.service.ts b/libs/features/texts/src/acore-string/select-acore-string.service.ts new file mode 100644 index 0000000000..4064d17873 --- /dev/null +++ b/libs/features/texts/src/acore-string/select-acore-string.service.ts @@ -0,0 +1,24 @@ +import { Injectable } from '@angular/core'; +import { SelectService } from '@keira/shared/base-abstract-classes'; +import { MysqlQueryService } from '@keira/shared/db-layer'; +import { + ACORE_STRING_DEFAULT, + ACORE_STRING_ENTRY, + ACORE_STRING_SEARCH_FIELDS, + ACORE_STRING_TABLE, + AcoreString, +} from '@keira/shared/acore-world-model'; +import { AcoreStringHandlerService } from './acore-string-handler.service'; + +@Injectable({ + providedIn: 'root', +}) +export class SelectAcoreStringService extends SelectService { + /* istanbul ignore next */ // because of: https://github.com/gotwarlost/istanbul/issues/690 + constructor( + override readonly queryService: MysqlQueryService, + public override readonly handlerService: AcoreStringHandlerService, + ) { + super(queryService, handlerService, ACORE_STRING_TABLE, ACORE_STRING_ENTRY, ACORE_STRING_DEFAULT, ACORE_STRING_SEARCH_FIELDS); + } +} diff --git a/libs/features/texts/src/index.ts b/libs/features/texts/src/index.ts index e1bd3e0158..c4209b8c94 100644 --- a/libs/features/texts/src/index.ts +++ b/libs/features/texts/src/index.ts @@ -9,3 +9,7 @@ export { BroadcastTextComponent } from './broadcast-text/broadcast-text.componen export { NpcTextHandlerService } from './npc-text/npc-text-handler.service'; export { SelectNpcTextComponent } from './npc-text/select-npc-text.component'; export { NpcTextComponent } from './npc-text/npc-text.component'; + +export { AcoreStringHandlerService } from './acore-string/acore-string-handler.service'; +export { SelectAcoreStringComponent } from './acore-string/select-acore-string.component'; +export { AcoreStringComponent } from './acore-string/acore-string.component'; diff --git a/libs/main/main-window/src/sidebar/sidebar.component.html b/libs/main/main-window/src/sidebar/sidebar.component.html index 76a1267bd7..29b29618a0 100644 --- a/libs/main/main-window/src/sidebar/sidebar.component.html +++ b/libs/main/main-window/src/sidebar/sidebar.component.html @@ -539,6 +539,22 @@