Skip to content
Merged
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
155 changes: 132 additions & 23 deletions cypress/integration/persons.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright (C) 2020 The Software Heritage developers
* Copyright (C) 2020-2025 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
Expand Down Expand Up @@ -395,31 +395,62 @@ describe('Author order change', function() {
});
});

it('moves roles with person', function() {
cy.get('#name').type('My Test Software');

cy.get('#author_add').click();
cy.get('#author_1_givenName').type('Jane');

cy.get('#author_add').click();
cy.get('#author_2_givenName').type('John');

cy.get('#author_1_role_add').click();
cy.get('#author_1_roleName_0').type('Developer');
cy.get('#author_1_roleName_0').should('have.value', 'Developer');

// Move author 1 to the right (swap with author 2)
cy.get('#author_1_moveToRight').click();

// After the swap, Jane (and her role) should be at author_2
cy.get('#author_2_givenName').should('have.value', 'Jane');
cy.get('#author_2_roleName_0').should('have.value', 'Developer');

// John should now be at author_1 and should not have the role
cy.get('#author_1_givenName').should('have.value', 'John');
cy.get('#author_1_roleName_0').should('not.exist');
});

it('wraps around to the right', function() {
cy.get('#name').type('My Test Software');

cy.get('#author_add').click();
cy.get('#author_add').click();
cy.get('#author_add').click();
cy.get('#author_1_givenName').type('Jane');
cy.get('#author_add').click();
cy.get('#author_1_givenName').type('One');
cy.get('#author_1_affiliation').type('Example Org');
cy.get('#author_2_givenName').type('John');
cy.get('#author_2_familyName').type('Doe');
cy.get('#author_3_givenName').type('Alex');
cy.get('#author_2_givenName').type('Two');
cy.get('#author_2_familyName').type('Too');
cy.get('#author_3_givenName').type('Three');
cy.get('#author_4_givenName').type('Four');

cy.get('#author_1_moveToLeft').click()

cy.get('#author_1_givenName').should('have.value', 'Alex');
cy.get('#author_1_familyName').should('have.value', '');
cy.get('#author_1_givenName').should('have.value', 'Two');
cy.get('#author_1_familyName').should('have.value', 'Too');
cy.get('#author_1_affiliation').should('have.value', '');

cy.get('#author_2_givenName').should('have.value', 'John');
cy.get('#author_2_familyName').should('have.value', 'Doe');
cy.get('#author_2_givenName').should('have.value', 'Three');
cy.get('#author_2_familyName').should('have.value', '');
cy.get('#author_2_affiliation').should('have.value', '');

cy.get('#author_3_givenName').should('have.value', 'Jane');
cy.get('#author_3_givenName').should('have.value', 'Four');
cy.get('#author_3_familyName').should('have.value', '');
cy.get('#author_3_affiliation').should('have.value', 'Example Org');
cy.get('#author_3_affiliation').should('have.value', '');

cy.get('#author_4_givenName').should('have.value', 'One');
cy.get('#author_4_familyName').should('have.value', '');
cy.get('#author_4_affiliation').should('have.value', 'Example Org');
});

it('wraps around to the left', function() {
Expand All @@ -428,25 +459,31 @@ describe('Author order change', function() {
cy.get('#author_add').click();
cy.get('#author_add').click();
cy.get('#author_add').click();
cy.get('#author_1_givenName').type('Jane');
cy.get('#author_add').click();
cy.get('#author_1_givenName').type('One');
cy.get('#author_1_affiliation').type('Example Org');
cy.get('#author_2_givenName').type('John');
cy.get('#author_2_familyName').type('Doe');
cy.get('#author_3_givenName').type('Alex');
cy.get('#author_2_givenName').type('Two');
cy.get('#author_2_familyName').type('Too');
cy.get('#author_3_givenName').type('Three');
cy.get('#author_4_givenName').type('Four');

cy.get('#author_3_moveToRight').click()
cy.get('#author_4_moveToRight').click()

cy.get('#author_1_givenName').should('have.value', 'Alex');
cy.get('#author_1_givenName').should('have.value', 'Four');
cy.get('#author_1_familyName').should('have.value', '');
cy.get('#author_1_affiliation').should('have.value', '');

cy.get('#author_2_givenName').should('have.value', 'John');
cy.get('#author_2_familyName').should('have.value', 'Doe');
cy.get('#author_2_affiliation').should('have.value', '');
cy.get('#author_2_givenName').should('have.value', 'One');
cy.get('#author_2_familyName').should('have.value', '');
cy.get('#author_2_affiliation').should('have.value', 'Example Org');

cy.get('#author_3_givenName').should('have.value', 'Jane');
cy.get('#author_3_familyName').should('have.value', '');
cy.get('#author_3_affiliation').should('have.value', 'Example Org');
cy.get('#author_3_givenName').should('have.value', 'Two');
cy.get('#author_3_familyName').should('have.value', 'Too');
cy.get('#author_3_affiliation').should('have.value', '');

cy.get('#author_4_givenName').should('have.value', 'Three');
cy.get('#author_4_familyName').should('have.value', '');
cy.get('#author_4_affiliation').should('have.value', '');
});
});

Expand Down Expand Up @@ -889,6 +926,78 @@ describe('Multiple authors', function () {
cy.get('#author_1_endDate_0').should('have.value', '2024-04-03');
cy.get('#author_2_givenName').should('have.value', 'Joe');
});

it('can remove the first one and reindexes remaining ones', function() {
cy.get('#name').type('My Test Software');

cy.get('#author_add').click();
cy.get('#author_add').click();
cy.get('#author_nb').should('have.value', '2');

cy.get('#author_1_givenName').type('Alice');
cy.get('#author_2_givenName').type('Bob');

cy.get('#author_1_remove').click();

cy.get('#author_nb').should('have.value', '1');
cy.get('#author_1_givenName').should('have.value', 'Bob');

cy.get('#generateCodemetaV2').click();
cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
.should('deep.equal', {
"@context": "https://doi.org/10.5063/schema/codemeta-2.0",
"type": "SoftwareSourceCode",
"name": "My Test Software",
"author": [
{
"id": "_:author_1",
"type": "Person",
"givenName": "Bob"
}
],
});
});

it('can remove a middle one and reindexes remaining ones', function() {
cy.get('#name').type('My Test Software');

cy.get('#author_add').click();
cy.get('#author_add').click();
cy.get('#author_add').click();
cy.get('#author_nb').should('have.value', '3');

cy.get('#author_1_givenName').type('Alice');
cy.get('#author_2_givenName').type('Bob');
cy.get('#author_3_givenName').type('Carol');

cy.get('#author_2_remove').click();

cy.get('#author_nb').should('have.value', '2');
cy.get('#author_1_givenName').should('have.value', 'Alice');
cy.get('#author_2_givenName').should('have.value', 'Carol');

cy.get('#generateCodemetaV2').click();
cy.get('#codemetaText').then((elem) => JSON.parse(elem.text()))
.should('deep.equal', {
"@context": "https://doi.org/10.5063/schema/codemeta-2.0",
"type": "SoftwareSourceCode",
"name": "My Test Software",
"author": [
{
"id": "_:author_1",
"type": "Person",
"givenName": "Alice"
},
{
"id": "_:author_2",
"type": "Person",
"givenName": "Carol"
}
],
});
});


});

describe('Contributors', function () {
Expand Down
4 changes: 4 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,8 @@ <h1>CodeMeta Generator v3.0</h1>
<input type="button" id="author_remove" value="Remove last"
onclick="removePerson('author');" />
</div>
<!-- author_list div is to be filled as the user adds authors -->
<div id="author_list"></div>
</fieldset>

<fieldset class="persons" id="contributor_container">
Expand All @@ -386,6 +388,8 @@ <h1>CodeMeta Generator v3.0</h1>
<input type="button" id="contributor_remove" value="Remove last"
onclick="removePerson('contributor');" />
</div>
<!-- contributor_list div is to be filled as the user adds contributors -->
<div id="contributor_list"></div>
</fieldset>
</div>

Expand Down
119 changes: 66 additions & 53 deletions js/codemeta_generation.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
/**
* Copyright (C) 2019-2020 The Software Heritage developers
* Copyright (C) 2019-2025 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/

"use strict";

let codemetaGenerationEnabled = true;

const CODEMETA_CONTEXTS = {
"2.0": {
path: "./data/contexts/codemeta-2.0.jsonld",
Expand Down Expand Up @@ -291,6 +293,12 @@ async function buildExpandedDocWithAllContexts() {

// v2.0 is still default version for generation, for now
async function generateCodemeta(codemetaVersion = "2.0") {
if (!codemetaGenerationEnabled) {
// Avoid regenerating a document while we are importing it.
// This avoid resetting the input in case there is an error in it.
return false;
}

var inputForm = document.querySelector('#inputForm');
var codemetaText, errorHTML;

Expand Down Expand Up @@ -427,68 +435,73 @@ async function recompactDocWithAllContexts(doc) {
}

async function importCodemeta() {
var inputForm = document.querySelector('#inputForm');
var doc = parseAndValidateCodemeta(false);
// Don't wipe the codemeta text (if any) in case of error
codemetaGenerationEnabled = false;

// Re-compact document with all contexts
// to allow importing property from any context
doc = await recompactDocWithAllContexts(doc);
try {
var doc = parseAndValidateCodemeta(false);
// Re-compact document with all contexts
// to allow importing property from any context
doc = await recompactDocWithAllContexts(doc);

resetForm();
resetForm();

if (doc['license'] !== undefined) {
if (typeof doc['license'] === 'string') {
doc['license'] = [doc['license']];
if (doc['license'] !== undefined) {
if (typeof doc['license'] === 'string') {
doc['license'] = [doc['license']];
}

doc['license'].forEach(l => {
if (l.indexOf(SPDX_PREFIX) !== 0) { return; }
let licenseId = l.substring(SPDX_PREFIX.length);
insertLicenseElement(licenseId);
});
}

doc['license'].forEach(l => {
if (l.indexOf(SPDX_PREFIX) !== 0) { return; }
let licenseId = l.substring(SPDX_PREFIX.length);
insertLicenseElement(licenseId);
directCodemetaFields.forEach(function (item, index) {
setIfDefined('#' + item, doc[item]);
});
}

directCodemetaFields.forEach(function (item, index) {
setIfDefined('#' + item, doc[item]);
});
importShortOrg('#funder', doc["funder"]);
importReview(doc["review"]);

// Import simple fields by joining on their separator
splittedCodemetaFields.forEach(function (item, index) {
const id = item[0];
const separator = item[1];
const serializer = item[2];
const deserializer = item[3];
let value = doc[id];
if (value !== undefined) {
if (Array.isArray(value)) {
if (deserializer !== undefined) {
value = value.map((item) => deserializer(id, item));
}
value = value.join(separator);
} else {
if (deserializer !== undefined) {
value = deserializer(id, value);
importShortOrg('#funder', doc["funder"]);
importReview(doc["review"]);

// Import simple fields by joining on their separator
splittedCodemetaFields.forEach(function (item, index) {
const id = item[0];
const separator = item[1];
const deserializer = item[3];
let value = doc[id];
if (value !== undefined) {
if (Array.isArray(value)) {
if (deserializer !== undefined) {
value = value.map((item) => deserializer(id, item));
}
value = value.join(separator);
} else {
if (deserializer !== undefined) {
value = deserializer(id, value);
}
}
setIfDefined('#' + id, value);
}
setIfDefined('#' + id, value);
}
});

for (const [key, items] of Object.entries(crossCodemetaFields)) {
let value = "";
items.forEach(item => {
value = doc[item] || value;
});
setIfDefined(`#${key}`, value);
}

importPersons('author', 'Author', doc['author']);
if (doc['contributor']) {
// If only one contributor, it is compacted to an object
const contributors = Array.isArray(doc['contributor'])? doc['contributor'] : [doc['contributor']];
importPersons('contributor', 'Contributor', contributors);
for (const [key, items] of Object.entries(crossCodemetaFields)) {
let value = "";
items.forEach(item => {
value = doc[item] || value;
});
setIfDefined(`#${key}`, value);
}

importPersons('author', 'Author', doc['author']);
if (doc['contributor']) {
// If only one contributor, it is compacted to an object
const contributors = Array.isArray(doc['contributor']) ? doc['contributor'] : [doc['contributor']];
importPersons('contributor', 'Contributor', contributors);
}
} finally {
// Re-enable codemeta generation
codemetaGenerationEnabled = true;
}
}

Expand Down
Loading