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
8 changes: 8 additions & 0 deletions code/+openminds/Node.m
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,14 @@
else
if ~isscalar(instance) % Preallocate object array
obj(1, numel(instance)) = feval( class(obj) );
% Growing the array fills the elements in between with
% copies of one default object, so those copies share
% the id that object generated. Every element gets its
% own blank node id here; fromStruct replaces it when
% the struct carries an identifier.
for i = 1:numel(obj)
obj(i).id = obj(i).generateInstanceId();
end
end
for i = 1:numel( instance )
% Todo: Separate method to handle reference structure.
Expand Down
30 changes: 30 additions & 0 deletions tools/tests/unitTests/CollectionTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,36 @@ function testSaveAndLoad(testCase)
testCase.verifyTrue(newCollection.isKey(org.id));
end

function testEmbeddedArrayOfFourSurvivesRoundTrip(testCase)
% A multi-valued embedded property is saved without identifiers
% and read back as a struct array. Each value must come back as a
% node of its own, or the owning node fails the uniqueness check
% on that property and is dropped from the loaded collection. Four
% values, so that the array holds more than one element between
% the first and the last.

hashes = openminds.core.data.Hash.empty;
for digest = ["1", "2", "3", "4"]
hashes(end+1) = openminds.core.data.Hash( ...
"algorithm", "SHA-1", "digest", digest); %#ok<AGROW>
end
file = openminds.core.data.File("name", "data.bin", ...
"IRI", "https://example.org/data.bin", "hash", hashes);

filePath = 'embedded-array.jsonld';
openminds.Collection(file).save(filePath);
reloaded = openminds.Collection(filePath);

testCase.assertTrue(reloaded.isKey(file.id), ...
'The node holding the embedded values was dropped on load.')
reloadedFile = reloaded.get(file.id);
testCase.assertNumElements(reloadedFile.hash, 4)
identifiers = arrayfun(@(h) string(h.id), reloadedFile.hash);
testCase.verifyEqual(numel(unique(identifiers)), 4, ...
'The embedded values share an identifier after loading.')
testCase.verifyEqual([reloadedFile.hash.digest], ["1", "2", "3", "4"])
end

function testSaveWithMetadataStoreOption(testCase)
% A store passed to save by name-value must be the store used.
% This went through obj.MetadataStore instead, which is empty
Expand Down
21 changes: 21 additions & 0 deletions tools/tests/unitTests/InstanceTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -98,5 +98,26 @@ function testSaveLoadForTypedInstance(testCase, MetadataType)
testCase.verifyEqual(S.itemPreSave, itemPreSave, ...
sprintf('Loaded object does not match the original for %s', metadataType.ClassName));
end

function testStructArrayWithoutIdsGetsDistinctIds(testCase)
% A multi-valued embedded property is read as a struct array with
% no identifiers, and each element must become a node of its own.
% Four elements, so that the array holds more than one element
% between the first and the last.

structs = struct( ...
'algorithm', {"SHA-1", "SHA-1", "SHA-1", "SHA-1"}, ...
'digest', {"1", "2", "3", "4"});

hashes = openminds.core.data.Hash(structs);

testCase.assertNumElements(hashes, 4)
identifiers = arrayfun(@(h) string(h.id), hashes);
testCase.verifyEqual(numel(unique(identifiers)), 4, ...
'Elements built from one struct array share an identifier.')
testCase.verifyTrue(all(startsWith(identifiers, "_:")), ...
'An element whose struct carries no identifier gets a blank node id.')
testCase.verifyEqual([hashes.digest], ["1", "2", "3", "4"])
end
end
end
Loading