diff --git a/src/models/MstColl.ts b/src/models/MstColl.ts index 7ca6258..241bb12 100644 --- a/src/models/MstColl.ts +++ b/src/models/MstColl.ts @@ -323,14 +323,24 @@ export const MstColl = types changeCollConstr(constr: any) {}, /** - * Adds single element or elements array into Coll's dataInternal + * Adds single element or elements array with unique @id into Coll's dataInternal + * Replaces elements with the same @id if values are different * @param elems -- element or element array */ addElems(elems: JsObject | JsObject[]) { if (!elems) return; if (!Array.isArray(elems)) elems = [elems]; - elems = elems.filter((elem: any) => !self.dataIntrnl.find((e: any) => e['@id'] === elem['@id'])); - self.dataIntrnl.push(elems); + const newElems: JsObject[] = []; + elems.forEach((elem: JsObject) => { + const existedElemIndx = self.dataIntrnl.findIndex((e: any) => e.get('@id') === elem['@id']); + if (existedElemIndx === -1) { + newElems.push(elem); + } else { + //@ts-ignore + self.updElemByIndx(existedElemIndx, elem); + } + }); + if (newElems.length > 0) self.dataIntrnl.push(...newElems); }, /** @@ -374,6 +384,19 @@ export const MstColl = types return null; }, + /** + * Update element in Coll by index regardless its @id mismatch + * @param indx + * @param elem + * @returns + */ + updElemByIndx(indx: number, elem: JsObject) { + const el = self.dataIntrnl[indx]; + applySnapshot(self.dataIntrnl[indx], elem); + //self.dataIntrnl.spliceWithArray(indx, 1, [elem]); + return el; + }, + /** * Update element in Coll with the same @id property as in elem object * @param elem -- object with @id property and other properties for update @@ -383,7 +406,8 @@ export const MstColl = types if (!elem || !elem['@id']) return null; const i = self.dataIntrnl.findIndex((e: any) => e.get('@id') === elem); if (i >= 0) { - return self.dataIntrnl.spliceWithArray(i, 1, elem); + //@ts-ignore + return self.updElemByIndx(i, elem); } return null; }, diff --git a/test/MstModel.spec.ts b/test/MstModel.spec.ts new file mode 100644 index 0000000..13e0bc7 --- /dev/null +++ b/test/MstModel.spec.ts @@ -0,0 +1,138 @@ +/******************************************************************************** + * Copyright (c) 2020 Agentlab and others. + * + * This program and the accompanying materials are made available under the + * terms of the GNU General Public License v. 3.0 which is available at + * https://www.gnu.org/licenses/gpl-3.0.html. + * + * SPDX-License-Identifier: GPL-3.0-only + ********************************************************************************/ +import { afterAll, beforeAll, describe, expect, jest, it } from '@jest/globals'; +import { compact, ContextDefinition } from 'jsonld'; + +import { rootModelInitialState } from '../src/models/Model'; +import { MstRepository } from '../src/models/MstRepository'; +import { SparqlClientImpl } from '../src/SparqlClientImpl'; +import { uploadFiles } from '../src/FileUpload'; + +import { expectToBeDefined, genTimestampedName } from './TestHelpers'; +import { SparqlClientImplMock } from './SparqlClientImplMock'; +import exp from 'constants'; +import { getSnapshot } from 'mobx-state-tree'; +import { JsObject } from '../src'; + +describe('MstModel_Coll', () => { + it(`MstModel_Coll should update `, async () => { + const client = new SparqlClientImplMock(); + const rootModelInitialStateWithColls = { + ...rootModelInitialState, + colls: { + 'rm:Artifacts_Coll': { + '@id': 'rm:Artifacts_Coll', + collConstr: { + '@id': 'rm:Artifacts_Coll', + '@type': 'aldkg:CollConstr', + entConstrs: [ + { + '@id': 'rm:Artifacts_Coll_Ent', + '@type': 'aldkg:EntConstr', + schema: 'rm:ArtifactShape', + conditions: {}, + data: {}, + }, + ], + }, + updPeriod: 300, + lazy: true, + dataIntrnl: [ + { + '@id': 'file:///urn-s2-iisvvt-infosystems-classifier-45950.xml', + '@type': 'rm:Artifact', + identifier: 30000, + title: 'ТН ВЭД ТС', + }, + { + '@id': 'cpgu:_tHAikozUEeOiy8owVBW5pQ', + '@type': 'rm:Artifact', + identifier: 30001, + title: 'ТН ВЭД ТС', + }, + { + '@id': 'cpgu:_zYXy8ozUEeOiy8owVBW5pQ', + '@type': 'rm:Artifact', + identifier: 30002, + title: 'Наименование раздела', + }, + { + '@id': 'cpgu:_3AP4kYzUEeOiy8owVBW5pQ', + '@type': 'rm:Artifact', + identifier: 30003, + title: 'Код товарной группы', + }, + ], + lastSynced: 1723761063624, + isLoading: false, + pageSize: 500, + resolveCollConstrs: true, + }, + }, + }; + const rep = MstRepository.create(rootModelInitialStateWithColls, { client }); + const coll = rep.getColl('rm:Artifacts_Coll'); + expectToBeDefined(coll); + expect(coll.dataIntrnl.length).toBe(4); + // add el + coll?.addElems({ + '@id': 'cpgu:_HmFCYozVEeOiy8owVBW5pQ', + '@type': 'rm:Artifact', + identifier: 30004, + title: 'Код товарной позиции', + }); + expect(coll.dataIntrnl.length).toBe(5); + const e1 = coll.dataByIri('cpgu:_HmFCYozVEeOiy8owVBW5pQ'); + expectToBeDefined(e1); + const e1Data: JsObject = getSnapshot(e1); + expect(e1Data.title).toBe('Код товарной позиции'); + // add two same els and one new el + coll.addElems([ + // updated el + { + '@id': 'cpgu:_tHAikozUEeOiy8owVBW5pQ', + '@type': 'rm:Artifact', + identifier: 30001, + title: 'ТН ВЭД ТС Changed', + }, + // same el + { + '@id': 'cpgu:_HmFCYozVEeOiy8owVBW5pQ', + '@type': 'rm:Artifact', + identifier: 30004, + title: 'Код товарной позиции', + }, + // new el + { + '@id': 'cpgu:_L8Lf8YzVEeOiy8owVBW5pQ', + '@type': 'rm:Artifact', + identifier: 30005, + title: 'Код товарной субпозиции', + }, + ]); + //console.log(coll?.dataJs); + expect(coll.dataIntrnl.length).toBe(6); + // check updated el + const e2 = coll.dataByIri('cpgu:_tHAikozUEeOiy8owVBW5pQ'); + expectToBeDefined(e2); + const e2Data: JsObject = getSnapshot(e2); + expect(e2Data.title).toBe('ТН ВЭД ТС Changed'); + // check same el + const e3 = coll.dataByIri('cpgu:_HmFCYozVEeOiy8owVBW5pQ'); + expectToBeDefined(e3); + const e3Data: JsObject = getSnapshot(e3); + expect(e3Data.title).toBe('Код товарной позиции'); + // check new added el + const e4 = coll.dataByIri('cpgu:_L8Lf8YzVEeOiy8owVBW5pQ'); + expectToBeDefined(e4); + const e4Data: JsObject = getSnapshot(e4); + expect(e4Data.title).toBe('Код товарной субпозиции'); + }); +}); diff --git a/test/TestHelpers.ts b/test/TestHelpers.ts index ee14631..5fdb606 100644 --- a/test/TestHelpers.ts +++ b/test/TestHelpers.ts @@ -19,12 +19,30 @@ export function sleep(ms: number): Promise { return new Promise((resolve: (value: any) => void) => setTimeout(resolve, ms)); } +/** + * Typescript assertion wrapper for the Jest's toBeDefined matcher + * See https://github.com/DefinitelyTyped/DefinitelyTyped/issues/41179#issuecomment-1920177478 + * @param arg + */ +export function expectToBeDefined(arg: T): asserts arg is Exclude { + expect(arg).toBeDefined(); +} + +/** + * Typescript assertion wrapper for the Jest's toBeUndefined matcher + * See https://github.com/DefinitelyTyped/DefinitelyTyped/issues/41179#issuecomment-1920177478 + * @param arg + */ +export function expectToBeUndefined(arg: unknown): asserts arg is undefined { + expect(arg).toBeUndefined(); +} + export async function selectHelper(repository: any, data: any, testerFn: (data: any) => void) { const coll = repository.addColl(data); await coll.loadColl(); - expect(coll).not.toBeUndefined(); + expectToBeDefined(coll); //console.log('artifact30000', json2str(artifact30000)); - const loadedData = coll && coll.data !== undefined ? getSnapshot(coll.data) : []; + const loadedData = coll.dataJs; testerFn(loadedData); repository.removeColl(coll); }