Most fields are optional. Mandatory fields will be highlighted when generating Codemeta.
codemeta.json:
diff --git a/README.md b/README.md index 8093a47..45d1857 100644 --- a/README.md +++ b/README.md @@ -1,103 +1,103 @@ # Codemeta Generator This repository contains a (client-side) web application to generate CodeMeta documents (aka. `codemeta.json`). The [CodeMeta initiative](https://github.com/codemeta/codemeta) is a Free and Open Source academic collaboration creating a minimal metadata schema for research software and code. The academic community recommands on adding a codemeta.json file in the root directory of your repository. With this linked data metadata file, you can easily declare the authorship, include contextual information and link to other research outputs (publications, data, etc.). Also, the `codemeta.json` file in your source code is indexed in the Software Heritage (SWH) archive, which will improve findability in searches. ### References - [SWH guidelines](https://www.softwareheritage.org/save-and-reference-research-software/) for research software. - [SWH blog post](https://www.softwareheritage.org/2019/05/28/mining-software-metadata-for-80-m-projects-and-even-more/) about metadata indexation. - [Dan S. Katz's blog post](https://danielskatzblog.wordpress.com/2017/09/25/software-heritage-and-repository-metadata-a-software-citation-solution/) about including metadata in your repository. - FORCE11's Software Citation Implementation WG [repository](https://github.com/force11/force11-sciwg) - RDA & FORCE11's joint Software Source Code Identification WG [repository](https://github.com/force11/force11-rda-scidwg) ## Specifications ### Use case 1. create a complete codemeta.json file from scratch 2. aggregate existing information and add complementary information to a codemeta.json file ### Functionalities - helpers while completing the form, for example a reference list of spdx licenses - a validation mechanism after submission - the possibility to use all the codeMeta terms and schema.org terms - accessible from multiple platforms (web browsers or OS) - (extra) the possibility to correct the output after validation as part of the creation process This tool was initially prepared for the [FORCE19 Hackathon](https://github.com/force11/force11-rda-scidwg/tree/master/hackathon/FORCE2019). +**NB:** codemeta v2.0 is generated by default, but v3.0 (v2.0 compatible) can be generated via a dedicated button. ## Code contributions. This section only applies to developers who want to contribute to the Codemeta Generator. If you only want to use it, you can use [the hosted version](https://codemeta.github.io/codemeta-generator/) instead. ### Code guidelines This application is designed to work on popular modern browsers (Firefox, Chromium/Google Chrome, Edge, Safari). Check [Caniuse](https://caniuse.com/) for availability of features for these browsers. To keep the architecture simple, we serve javascript files directly to -browsers, without a compiler or transpiler; and do not use third-party -dependencies for now. +browsers, without a compiler or transpiler. ### Running local changes To run Codemeta Generator, you just need an HTTP server serving the files (nginx, apache2, etc.). The simplest way is probably to use Python's HTTP server: ``` git clone https://github.com/codemeta/codemeta-generator cd codemeta-generator python3 -m http.server ``` then open [http://localhost:8000/](http://localhost:8000/) in your web browser. ### Automatic testing In addition to manual testing, we have automated tests to check for bugs quickly, using [Cypress](https://www.cypress.io/). To run them, first install Cypress: ``` sudo apt install npm # or the equivalent on your system npx cypress@9.7.0 install ``` Then, run the tests: ``` npx cypress@9.7.0 run ``` ## Contributed by  diff --git a/cypress/integration/basics.js b/cypress/integration/basics.js index 4e1e575..2cfd994 100644 --- a/cypress/integration/basics.js +++ b/cypress/integration/basics.js @@ -1,267 +1,364 @@ /** * Copyright (C) 2020 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 */ /* * Tests the basic features of the application. */ "use strict"; describe('JSON Generation', function() { beforeEach(function() { /* Clear the session storage, as it is used to restore field data; * and we don't want a test to load data from the previous test. */ cy.window().then((win) => { win.sessionStorage.clear() }) cy.visit('./index.html'); }); it('works just from the software name', function() { cy.get('#name').type('My Test Software'); cy.get('#generateCodemetaV2').click(); cy.get('#errorMessage').should('have.text', ''); 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", }); }); it('works just from all main fields when using only one license', function() { cy.get('#name').type('My Test Software'); cy.get('#description').type('This is a\ngreat piece of software'); cy.get('#dateCreated').type('2019-10-02'); cy.get('#datePublished').type('2020-01-01'); cy.get('#license').type('AGPL-3.0'); cy.get("#license").type('{enter}'); cy.get('#generateCodemetaV2').click(); cy.get("#license").should('have.value', ''); cy.get('#errorMessage').should('have.text', ''); cy.get('#codemetaText').then((elem) => JSON.parse(elem.text())) .should('deep.equal', { "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "type": "SoftwareSourceCode", "license": "https://spdx.org/licenses/AGPL-3.0", "dateCreated": "2019-10-02", "datePublished": "2020-01-01", "name": "My Test Software", "description": "This is a\ngreat piece of software", }); }); it('works just from all main fields when using multiple licenses', function() { cy.get('#name').type('My Test Software'); cy.get('#description').type('This is a\ngreat piece of software'); cy.get('#dateCreated').type('2019-10-02'); cy.get('#datePublished').type('2020-01-01'); cy.get('#license').type('AGPL-3.0'); cy.get("#license").type('{enter}'); cy.get('#license').type('MIT'); cy.get("#license").type('{enter}'); cy.get('#generateCodemetaV2').click(); cy.get("#license").should('have.value', ''); cy.get('#errorMessage').should('have.text', ''); cy.get('#codemetaText').then((elem) => JSON.parse(elem.text())) .should('deep.equal', { "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "type": "SoftwareSourceCode", "license": ["https://spdx.org/licenses/AGPL-3.0", "https://spdx.org/licenses/MIT"], "dateCreated": "2019-10-02", "datePublished": "2020-01-01", "name": "My Test Software", "description": "This is a\ngreat piece of software", }); }); it('works when choosing licenses without the keyboard', function() { cy.get('#name').type('My Test Software'); cy.get('#description').type('This is a\ngreat piece of software'); cy.get('#dateCreated').type('2019-10-02'); cy.get('#datePublished').type('2020-01-01'); cy.get('#license').type('AGPL-3.0'); // no cy.get("#license").type('{enter}'); here cy.get('#generateCodemetaV2').click(); cy.get("#license").should('have.value', ''); cy.get('#errorMessage').should('have.text', ''); cy.get('#codemetaText').then((elem) => JSON.parse(elem.text())) .should('deep.equal', { "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "type": "SoftwareSourceCode", "license": "https://spdx.org/licenses/AGPL-3.0", "dateCreated": "2019-10-02", "datePublished": "2020-01-01", "name": "My Test Software", "description": "This is a\ngreat piece of software", }); }); it('works for new codemeta terms in both versions', function() { cy.get('#name').type('My Test Software'); cy.get('#contIntegration').type('https://test-ci.org/my-software'); cy.get('#isSourceCodeOf').type('Bigger Application'); cy.get('#reviewAspect').type('Some software aspect'); cy.get('#reviewBody').type('Some review'); cy.get('#generateCodemetaV2').click(); cy.get('#errorMessage').should('have.text', ''); 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", "contIntegration": "https://test-ci.org/my-software", "codemeta:continuousIntegration": { "id": "https://test-ci.org/my-software" }, "codemeta:isSourceCodeOf": { "id": "Bigger Application" }, "schema:review": { "type": "schema:Review", "schema:reviewAspect": "Some software aspect", "schema:reviewBody": "Some review" } }); cy.get('#generateCodemetaV3').click(); cy.get('#errorMessage').should('have.text', ''); cy.get('#codemetaText').then((elem) => JSON.parse(elem.text())) .should('deep.equal', { "@context": "https://w3id.org/codemeta/3.0", "type": "SoftwareSourceCode", "name": "My Test Software", "continuousIntegration": "https://test-ci.org/my-software", "codemeta:contIntegration": { "id": "https://test-ci.org/my-software" }, "isSourceCodeOf": "Bigger Application", "review": { "type": "Review", "reviewAspect": "Some software aspect", "reviewBody": "Some review" } }); }); }); describe('JSON Import', function() { it('works just from the software name', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "name": "My Test Software", })) ); cy.get('#importCodemeta').click(); cy.get('#name').should('have.value', 'My Test Software'); }); it('works just from all main fields when using license as string', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "license": "https://spdx.org/licenses/AGPL-3.0", "dateCreated": "2019-10-02", "datePublished": "2020-01-01", "name": "My Test Software", "description": "This is a\ngreat piece of software", })) ); cy.get('#importCodemeta').click(); cy.get('#name').should('have.value', 'My Test Software'); cy.get('#description').should('have.value', 'This is a\ngreat piece of software'); cy.get('#dateCreated').should('have.value', '2019-10-02'); cy.get('#datePublished').should('have.value', '2020-01-01'); cy.get('#license').should('have.value', ''); cy.get("#selected-licenses").children().should('have.length', 1); cy.get("#selected-licenses").children().first().children().first().should('have.text', 'AGPL-3.0'); }); it('works just from all main fields when using license as array', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "license": ["https://spdx.org/licenses/AGPL-3.0", "https://spdx.org/licenses/MIT"], "dateCreated": "2019-10-02", "datePublished": "2020-01-01", "name": "My Test Software", "description": "This is a\ngreat piece of software", })) ); cy.get('#importCodemeta').click(); cy.get('#name').should('have.value', 'My Test Software'); cy.get('#description').should('have.value', 'This is a\ngreat piece of software'); cy.get('#dateCreated').should('have.value', '2019-10-02'); cy.get('#datePublished').should('have.value', '2020-01-01'); cy.get('#license').should('have.value', ''); cy.get("#selected-licenses").children().should('have.length', 2); cy.get("#selected-licenses").children().eq(0).children().first().should('have.text', 'AGPL-3.0'); cy.get("#selected-licenses").children().eq(1).children().first().should('have.text', 'MIT'); }); it('works with expanded document version', function () { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "http://schema.org/name": [ { "@value": "My Test Software" } ], "@type": [ "http://schema.org/SoftwareSourceCode" ] })) ); cy.get('#importCodemeta').click(); cy.get('#name').should('have.value', 'My Test Software'); }); it('errors on invalid type', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "foo", "name": "My Test Software", })) ); cy.get('#importCodemeta').click(); // Should still be imported as much as possible cy.get('#name').should('have.value', 'My Test Software'); // But must display an error cy.get('#errorMessage').should('have.text', 'Wrong document type: must be "SoftwareSourceCode"/"SoftwareApplication", not "foo"'); }); it('allows singleton array as context', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": ["https://doi.org/10.5063/schema/codemeta-2.0"], "@type": "SoftwareSourceCode", "name": "My Test Software", })) ); cy.get('#importCodemeta').click(); cy.get('#name').should('have.value', 'My Test Software'); }); + it('imports properties introduced in codemeta v3.0', function() { + cy.get('#codemetaText').then((elem) => + elem.text(JSON.stringify({ + "@context": "https://w3id.org/codemeta/3.0", + "type": "SoftwareSourceCode", + "name": "My Test Software", + "continuousIntegration": "https://test-ci.org/my-software", + "isSourceCodeOf": "Bigger Application", + "review": { + "type": "Review", + "reviewAspect": "Some software aspect", + "reviewBody": "Some review" + } + })) + ); + cy.get('#importCodemeta').click(); + + cy.get('#contIntegration').should('have.value', 'https://test-ci.org/my-software'); + cy.get('#isSourceCodeOf').should('have.value', 'Bigger Application'); + cy.get('#reviewAspect').should('have.value', 'Some software aspect'); + cy.get('#reviewBody').should('have.value', 'Some review'); + }); + + it('imports codemeta v2.0 properties from document with v3.0 context', function() { + cy.get('#codemetaText').then((elem) => + elem.text(JSON.stringify({ + "@context": "https://w3id.org/codemeta/3.0", + "type": "SoftwareSourceCode", + "name": "My Test Software", + "codemeta:contIntegration": { + "id": "https://test-ci.org/my-software" + } + })) + ); + cy.get('#importCodemeta').click(); + + cy.get('#contIntegration').should('have.value', 'https://test-ci.org/my-software'); + }); + + it('imports codemeta v3.0 properties from document with v2.0 context', function() { + cy.get('#codemetaText').then((elem) => + elem.text(JSON.stringify({ + "@context": "https://doi.org/10.5063/schema/codemeta-2.0", + "type": "SoftwareSourceCode", + "name": "My Test Software", + "codemeta:continuousIntegration": { + "id": "https://test-ci.org/my-software" + }, + "codemeta:isSourceCodeOf": { + "id": "Bigger Application" + }, + "schema:review": { + "type": "schema:Review", + "schema:reviewAspect": "Some software aspect", + "schema:reviewBody": "Some review" + } + })) + ); + cy.get('#importCodemeta').click(); + + cy.get('#contIntegration').should('have.value', 'https://test-ci.org/my-software'); + cy.get('#isSourceCodeOf').should('have.value', 'Bigger Application'); + cy.get('#reviewAspect').should('have.value', 'Some software aspect'); + cy.get('#reviewBody').should('have.value', 'Some review'); + }); + + it('imports newest version property when it is duplicate in multiple version context', function() { + cy.get('#codemetaText').then((elem) => + elem.text(JSON.stringify({ + "@context": "https://doi.org/10.5063/schema/codemeta-2.0", + "type": "SoftwareSourceCode", + "name": "My Test Software", + "contIntegration": "https://test-ci1.org/my-software", + "codemeta:continuousIntegration": { + "id": "https://test-ci2.org/my-software" + }, + })) + ); + cy.get('#importCodemeta').click(); + + cy.get('#contIntegration').should('have.value', 'https://test-ci2.org/my-software'); + + cy.get('#codemetaText').then((elem) => + elem.text(JSON.stringify({ + "@context": "https://doi.org/10.5063/schema/codemeta-3.0", + "type": "SoftwareSourceCode", + "name": "My Test Software", + "continuousIntegration": "https://test-ci1.org/my-software", + "codemeta:contIntegration": { + "id": "https://test-ci2.org/my-software" + }, + })) + ); + cy.get('#importCodemeta').click(); + + cy.get('#contIntegration').should('have.value', 'https://test-ci1.org/my-software'); + }); }); diff --git a/cypress/integration/persons.js b/cypress/integration/persons.js index 7516167..b3b68ce 100644 --- a/cypress/integration/persons.js +++ b/cypress/integration/persons.js @@ -1,686 +1,850 @@ /** * Copyright (C) 2020 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 */ /* * Tests the author/contributor dynamic fieldsets */ "use strict"; describe('Zero author', function() { it('can be exported', function() { cy.get('#name').type('My Test Software'); cy.get('#generateCodemetaV2').click(); cy.get('#errorMessage').should('have.text', ''); 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", }); }); it('can be imported from no list', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "name": "My Test Software", })) ); cy.get('#importCodemeta').click(); cy.get('#author_nb').should('have.value', '0'); cy.get('#author_0').should('not.exist'); cy.get('#author_1').should('not.exist'); }); it('can be imported from empty list', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "name": "My Test Software", "author": [], })) ); cy.get('#importCodemeta').click(); cy.get('#author_nb').should('have.value', '0'); cy.get('#author_0').should('not.exist'); cy.get('#author_1').should('not.exist'); }); }); describe('One full author', function() { it('can be exported', function() { cy.get('#name').type('My Test Software'); cy.get('#author_nb').should('have.value', '0'); cy.get('#author_0').should('not.exist'); cy.get('#author_1').should('not.exist'); cy.get('#author_1_givenName').should('not.exist'); cy.get('#author_add').click(); cy.get('#author_nb').should('have.value', '1'); cy.get('#author_0').should('not.exist'); cy.get('#author_1').should('exist'); cy.get('#author_2').should('not.exist'); cy.get('#author_1_givenName').should('have.value', ''); cy.get('#author_1_familyName').should('have.value', ''); cy.get('#author_1_email').should('have.value', ''); cy.get('#author_1_id').should('have.value', ''); cy.get('#author_1_affiliation').should('have.value', ''); cy.get('#author_1_givenName').type('Jane'); cy.get('#author_1_familyName').type('Doe'); cy.get('#author_1_email').type('jdoe@example.org'); cy.get('#author_1_id').type('http://example.org/~jdoe'); cy.get('#author_1_affiliation').type('http://example.org/'); cy.get('#generateCodemetaV2').click(); cy.get('#errorMessage').should('have.text', ''); 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": [ { "type": "Person", "id": "http://example.org/~jdoe", "givenName": "Jane", "familyName": "Doe", "email": "jdoe@example.org", "affiliation": { "type": "Organization", "id": "http://example.org/", } } ], }); }); - it('can be imported', function() { + it('can be imported even if there is also a role-less author', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { "@type": "Person", "@id": "http://example.org/~jdoe", "givenName": "Jane", "familyName": "Doe", "email": "jdoe@example.org", "affiliation": { "@type": "Organization", "@id": "http://example.org/", } } ], })) ); cy.get('#importCodemeta').click(); cy.get('#author_nb').should('have.value', '1'); cy.get('#author_0').should('not.exist'); cy.get('#author_1').should('exist'); cy.get('#author_2').should('not.exist'); cy.get('#author_1_givenName').should('have.value', 'Jane'); cy.get('#author_1_familyName').should('have.value', 'Doe'); cy.get('#author_1_email').should('have.value', 'jdoe@example.org'); cy.get('#author_1_id').should('have.value', 'http://example.org/~jdoe'); cy.get('#author_1_affiliation').should('have.value', 'http://example.org/'); }); }); describe('Affiliation id', function() { it('can be exported', function() { cy.get('#name').type('My Test Software'); cy.get('#author_add').click(); cy.get('#author_1_givenName').type('Jane'); cy.get('#author_1_affiliation').type('http://example.org/'); cy.get('#generateCodemetaV2').click(); cy.get('#errorMessage').should('have.text', ''); 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": [ { "type": "Person", "givenName": "Jane", "affiliation": { "type": "Organization", "id": "http://example.org/", } } ], }); }); it('can be imported', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { "@type": "Person", "@id": "http://example.org/~jdoe", "givenName": "Jane", "familyName": "Doe", "email": "jdoe@example.org", "affiliation": { "@type": "Organization", "@id": "http://example.org/", } } ], })) ); cy.get('#importCodemeta').click(); cy.get('#author_nb').should('have.value', '1'); cy.get('#author_0').should('not.exist'); cy.get('#author_1').should('exist'); cy.get('#author_2').should('not.exist'); cy.get('#author_1_affiliation').should('have.value', 'http://example.org/'); }); }); describe('Affiliation name', function() { it('can be exported', function() { cy.get('#name').type('My Test Software'); cy.get('#author_add').click(); cy.get('#author_1_givenName').type('Jane'); cy.get('#author_1_affiliation').type('Example Org'); cy.get('#generateCodemetaV2').click(); cy.get('#errorMessage').should('have.text', ''); 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": [ { "type": "Person", "givenName": "Jane", "affiliation": { "type": "Organization", "name": "Example Org", } } ], }); }); it('can be imported', function() { cy.get('#codemetaText').then((elem) => elem.text(JSON.stringify({ "@context": "https://doi.org/10.5063/schema/codemeta-2.0", "@type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { "@type": "Person", "@id": "http://example.org/~jdoe", "givenName": "Jane", "familyName": "Doe", "email": "jdoe@example.org", "affiliation": { "@type": "Organization", "name": "Example Org", } } ], })) ); cy.get('#importCodemeta').click(); cy.get('#author_nb').should('have.value', '1'); cy.get('#author_0').should('not.exist'); cy.get('#author_1').should('exist'); cy.get('#author_2').should('not.exist'); cy.get('#author_1_affiliation').should('have.value', 'Example Org'); }); }); describe('Author order change', function() { it('is a noop with a single author', function() { cy.get('#name').type('My Test Software'); cy.get('#author_add').click(); cy.get('#author_1_givenName').type('Jane'); cy.get('#author_1_affiliation').type('Example Org'); cy.get('#author_1_moveToRight').click(); cy.get('#author_1_givenName').should('have.value', 'Jane'); cy.get('#author_1_affiliation').should('have.value', 'Example Org'); cy.get('#author_1_moveToLeft').click(); cy.get('#author_1_givenName').should('have.value', 'Jane'); cy.get('#author_1_affiliation').should('have.value', 'Example Org'); }); it('flips two authors', 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_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_1_moveToRight').click(); cy.get('#author_1_givenName').should('have.value', 'John'); cy.get('#author_1_familyName').should('have.value', 'Doe'); cy.get('#author_1_affiliation').should('have.value', ''); cy.get('#author_2_givenName').should('have.value', 'Jane'); 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', 'Alex'); cy.get('#author_3_familyName').should('have.value', ''); cy.get('#author_3_affiliation').should('have.value', ''); }); it('updates generated Codemeta', function() { cy.get('#name').type('My Test Software'); cy.get('#author_add').click(); cy.get('#author_add').click(); cy.get('#author_1_givenName').type('Jane'); cy.get('#author_1_affiliation').type('Example Org'); cy.get('#author_2_givenName').type('John'); cy.get('#author_2_familyName').type('Doe'); 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": [ { "type": "Person", "givenName": "Jane", "affiliation": { "type": "Organization", "name": "Example Org", } }, { "type": "Person", "givenName": "John", "familyName": "Doe", }, ], }); cy.get('#author_1_moveToRight').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": [ { "type": "Person", "givenName": "John", "familyName": "Doe", }, { "type": "Person", "givenName": "Jane", "affiliation": { "type": "Organization", "name": "Example Org", } }, ], }); }); 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_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_1_moveToLeft').click() cy.get('#author_1_givenName').should('have.value', 'Alex'); 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_3_givenName').should('have.value', 'Jane'); cy.get('#author_3_familyName').should('have.value', ''); cy.get('#author_3_affiliation').should('have.value', 'Example Org'); }); it('wraps around to the left', 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_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_3_moveToRight').click() cy.get('#author_1_givenName').should('have.value', 'Alex'); 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_3_givenName').should('have.value', 'Jane'); cy.get('#author_3_familyName').should('have.value', ''); cy.get('#author_3_affiliation').should('have.value', 'Example Org'); }); }); describe('One author with a role', function () { it('can be exported in both codemeta v2.0 and v3.0 versions', function () { cy.get('#name').type('My Test Software'); cy.get('#author_add').click(); cy.get('#author_1_givenName').type('Jane'); cy.get('#author_1_role_add').click(); cy.get('#author_1_roleName_0').type('Developer'); cy.get('#author_1_startDate_0').type('2024-03-04'); cy.get('#author_1_endDate_0').type('2024-04-03'); 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": [ { "type": "Person", "givenName": "Jane" }, { "type": "schema:Role", "schema:author": { "type": "Person", "givenName": "Jane" }, "schema:roleName": "Developer", "schema:startDate": "2024-03-04", "schema:endDate": "2024-04-03" } ] }); cy.get('#generateCodemetaV3').click(); cy.get('#codemetaText').then((elem) => JSON.parse(elem.text())) .should('deep.equal', { "@context": "https://w3id.org/codemeta/3.0", "type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { "type": "Person", "givenName": "Jane" }, { "type": "Role", "schema:author": { "type": "Person", "givenName": "Jane" }, "roleName": "Developer", "startDate": "2024-03-04", "endDate": "2024-04-03" } ] }); }); it('can have two roles', function () { cy.get('#name').type('My Test Software'); cy.get('#author_add').click(); cy.get('#author_1_givenName').type('Jane'); cy.get('#author_1_role_add').click(); cy.get('#author_1_roleName_0').type('Developer'); cy.get('#author_1_startDate_0').type('2024-03-04'); cy.get('#author_1_endDate_0').type('2024-04-03'); cy.get('#author_1_role_add').click(); cy.get('#author_1_roleName_1').type('Maintainer'); cy.get('#author_1_startDate_1').type('2024-04-04'); cy.get('#author_1_endDate_1').type('2024-05-05'); cy.get('#generateCodemetaV3').click(); cy.get('#codemetaText').then((elem) => JSON.parse(elem.text())) .should('deep.equal', { "@context": "https://w3id.org/codemeta/3.0", "type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { "type": "Person", "givenName": "Jane" }, { "type": "Role", "schema:author": { "type": "Person", "givenName": "Jane" }, "roleName": "Maintainer", "startDate": "2024-04-04", "endDate": "2024-05-05" }, { "type": "Role", "schema:author": { "type": "Person", "givenName": "Jane" }, "roleName": "Developer", "startDate": "2024-03-04", "endDate": "2024-04-03" } ] }); }); it('can be deleted then added again', function () { cy.get('#name').type('My Test Software'); cy.get('#author_add').click(); cy.get('#author_1_givenName').type('Jane'); cy.get('#author_1_role_add').click(); cy.get('#author_1_roleName_0').type('Developer'); cy.get('#author_1_startDate_0').type('2024-03-04'); cy.get('#author_1_endDate_0').type('2024-04-03'); cy.get('#author_1_role_remove_0').click(); cy.get('#author_1_role_add').click(); cy.get('#author_1_roleName_1').type('Maintainer'); cy.get('#author_1_startDate_1').type('2024-04-04'); cy.get('#author_1_endDate_1').type('2024-05-05'); cy.get('#generateCodemetaV3').click(); cy.get('#codemetaText').then((elem) => JSON.parse(elem.text())) .should('deep.equal', { "@context": "https://w3id.org/codemeta/3.0", "type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { "type": "Person", "givenName": "Jane" }, { "type": "Role", "schema:author": { "type": "Person", "givenName": "Jane" }, "roleName": "Maintainer", "startDate": "2024-04-04", "endDate": "2024-05-05" } ] }); }); it('can be imported', function () { - // TODO + cy.get('#codemetaText').then((elem) => + elem.text(JSON.stringify({ + "@context": "https://w3id.org/codemeta/3.0", + "type": "SoftwareSourceCode", + "name": "My Test Software", + "author": [ + { + "type": "Person", + "givenName": "Jane" + }, + { + "type": "Role", + "schema:author": { + "type": "Person", + "givenName": "Jane" + }, + "roleName": "Developer", + "startDate": "2024-03-04", + "endDate": "2024-04-03" + } + ] + })) + ); + cy.get('#importCodemeta').click(); + + cy.get('#author_nb').should('have.value', '1'); + cy.get('#author_1_givenName').should('have.value', 'Jane'); + cy.get('#author_1_roleName_0').should('have.value', 'Developer'); + cy.get('#author_1_startDate_0').should('have.value', '2024-03-04'); + cy.get('#author_1_endDate_0').should('have.value', '2024-04-03'); + }); + + it('can be imported when there is a second one, and they are merged', function () { + cy.get('#codemetaText').then((elem) => + elem.text(JSON.stringify({ + "@context": "https://w3id.org/codemeta/3.0", + "type": "SoftwareSourceCode", + "name": "My Test Software", + "author": [ + { + "type": "Person", + "givenName": "Jane" + }, + { + "type": "Role", + "schema:author": { + "type": "Person", + "givenName": "Jane" + }, + "roleName": "Maintainer", + "startDate": "2024-04-04", + "endDate": "2024-05-05" + }, + { + "type": "Role", + "schema:author": { + "type": "Person", + "givenName": "Jane" + }, + "roleName": "Developer", + "startDate": "2024-03-04", + "endDate": "2024-04-03" + } + ] + })) + ); + cy.get('#importCodemeta').click(); + + cy.get('#author_nb').should('have.value', '1'); + cy.get('#author_1_givenName').should('have.value', 'Jane'); + cy.get('#author_1_roleName_0').should('have.value', 'Maintainer'); + cy.get('#author_1_startDate_0').should('have.value', '2024-04-04'); + cy.get('#author_1_endDate_0').should('have.value', '2024-05-05'); + cy.get('#author_1_roleName_1').should('have.value', 'Developer'); + cy.get('#author_1_startDate_1').should('have.value', '2024-03-04'); + cy.get('#author_1_endDate_1').should('have.value', '2024-04-03'); }); }); describe('Multiple authors', function () { it('who both have roles can be exported', 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('Joe'); cy.get('#author_1_role_add').click(); cy.get('#author_1_roleName_0').type('Developer'); cy.get('#author_1_startDate_0').type('2024-03-04'); cy.get('#author_1_endDate_0').type('2024-04-03'); cy.get('#author_2_role_add').click(); cy.get('#author_2_roleName_0').type('Maintainer'); cy.get('#author_2_startDate_0').type('2024-04-04'); cy.get('#author_2_endDate_0').type('2024-05-05'); cy.get('#generateCodemetaV3').click(); cy.get('#codemetaText').then((elem) => JSON.parse(elem.text())) .should('deep.equal', { "@context": "https://w3id.org/codemeta/3.0", "type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { "type": "Person", "givenName": "Jane" }, { "type": "Role", "schema:author": { "type": "Person", "givenName": "Jane" }, "roleName": "Developer", "startDate": "2024-03-04", "endDate": "2024-04-03" }, { "type": "Person", "givenName": "Joe" }, { "type": "Role", "schema:author": { "type": "Person", "givenName": "Joe" }, "roleName": "Maintainer", "startDate": "2024-04-04", "endDate": "2024-05-05" } ] }); }); it('whose one has a role and the other not can be exported', 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('Joe'); cy.get('#author_1_role_add').click(); cy.get('#author_1_roleName_0').type('Developer'); cy.get('#author_1_startDate_0').type('2024-03-04'); cy.get('#author_1_endDate_0').type('2024-04-03'); cy.get('#generateCodemetaV3').click(); cy.get('#codemetaText').then((elem) => JSON.parse(elem.text())) .should('deep.equal', { "@context": "https://w3id.org/codemeta/3.0", "type": "SoftwareSourceCode", "name": "My Test Software", "author": [ { "type": "Person", "givenName": "Jane" }, { "type": "Role", "schema:author": { "type": "Person", "givenName": "Jane" }, "roleName": "Developer", "startDate": "2024-03-04", "endDate": "2024-04-03" }, { "type": "Person", "givenName": "Joe" } ] }); }); + + it('who both have roles can be imported', function () { + cy.get('#codemetaText').then((elem) => + elem.text(JSON.stringify({ + "@context": "https://w3id.org/codemeta/3.0", + "type": "SoftwareSourceCode", + "name": "My Test Software", + "author": [ + { + "type": "Person", + "givenName": "Jane" + }, + { + "type": "Role", + "schema:author": { + "type": "Person", + "givenName": "Jane" + }, + "roleName": "Developer", + "startDate": "2024-03-04", + "endDate": "2024-04-03" + }, + { + "type": "Person", + "givenName": "Joe" + }, + { + "type": "Role", + "schema:author": { + "type": "Person", + "givenName": "Joe" + }, + "roleName": "Maintainer", + "startDate": "2024-04-04", + "endDate": "2024-05-05" + } + ] + })) + ); + cy.get('#importCodemeta').click(); + + cy.get('#author_nb').should('have.value', '2'); + cy.get('#author_1_givenName').should('have.value', 'Jane'); + cy.get('#author_1_roleName_0').should('have.value', 'Developer'); + cy.get('#author_1_startDate_0').should('have.value', '2024-03-04'); + cy.get('#author_1_endDate_0').should('have.value', '2024-04-03'); + cy.get('#author_2_givenName').should('have.value', 'Joe'); + cy.get('#author_2_roleName_0').should('have.value', 'Maintainer'); + cy.get('#author_2_startDate_0').should('have.value', '2024-04-04'); + cy.get('#author_2_endDate_0').should('have.value', '2024-05-05'); + }); + + it('whose one has a role and the other not can be imported', function () { + cy.get('#codemetaText').then((elem) => + elem.text(JSON.stringify({ + "@context": "https://w3id.org/codemeta/3.0", + "type": "SoftwareSourceCode", + "name": "My Test Software", + "author": [ + { + "type": "Person", + "givenName": "Jane" + }, + { + "type": "Role", + "schema:author": { + "type": "Person", + "givenName": "Jane" + }, + "roleName": "Developer", + "startDate": "2024-03-04", + "endDate": "2024-04-03" + }, + { + "type": "Person", + "givenName": "Joe" + } + ] + })) + ); + cy.get('#importCodemeta').click(); + + cy.get('#author_nb').should('have.value', '2'); + cy.get('#author_1_givenName').should('have.value', 'Jane'); + cy.get('#author_1_roleName_0').should('have.value', 'Developer'); + cy.get('#author_1_startDate_0').should('have.value', '2024-03-04'); + cy.get('#author_1_endDate_0').should('have.value', '2024-04-03'); + cy.get('#author_2_givenName').should('have.value', 'Joe'); + }); }); diff --git a/index.html b/index.html index 3af9815..cda6dc8 100644 --- a/index.html +++ b/index.html @@ -1,394 +1,394 @@
Most fields are optional. Mandatory fields will be highlighted when generating Codemeta.
codemeta.json:
`; return fieldset; } function addPersonWithId(container, prefix, legend, id) { var personPrefix = `${prefix}_${id}`; var fieldset = createPersonFieldset(personPrefix, `${legend} #${id}`); container.appendChild(fieldset); document.querySelector(`#${personPrefix}_moveToLeft`) .addEventListener('click', () => movePerson(prefix, id, "left")); document.querySelector(`#${personPrefix}_moveToRight`) .addEventListener('click', () => movePerson(prefix, id, "right")); document.querySelector(`#${personPrefix}_role_add`) .addEventListener('click', () => addRole(personPrefix)); } function movePerson(prefix, id1, direction) { var nbPersons = getNbPersons(prefix); var id2; // Computer id2, the id of the person to flip id1 with (wraps around the // end of the list of persons) if (direction == "left") { id2 = id1 - 1; if (id2 <= 0) { id2 = nbPersons; } } else { id2 = id1 + 1; if (id2 > nbPersons) { id2 = 1; } } // Flip the field values, one by one personFields.forEach((fieldName) => { var field1 = document.querySelector(`#${prefix}_${id1}_${fieldName}`); var field2 = document.querySelector(`#${prefix}_${id2}_${fieldName}`); var value1 = field1.value; var value2 = field2.value; field2.value = value1; field1.value = value2; }); // Form was changed; regenerate generateCodemeta(); } function addPerson(prefix, legend) { var container = document.querySelector(`#${prefix}_container`); var personId = getNbPersons(prefix) + 1; addPersonWithId(container, prefix, legend, personId); setNbPersons(prefix, personId); return personId; } function removePerson(prefix) { var personId = getNbPersons(prefix); document.querySelector(`#${prefix}_${personId}`).remove(); setNbPersons(prefix, personId - 1); } // Initialize a group of persons (authors, contributors) on page load. // Useful if the page is reloaded. function initPersons(prefix, legend) { var nbPersons = getNbPersons(prefix); var personContainer = document.querySelector(`#${prefix}_container`) for (let personId = 1; personId <= nbPersons; personId++) { addPersonWithId(personContainer, prefix, legend, personId); } } function removePersons(prefix) { var nbPersons = getNbPersons(prefix); var personContainer = document.querySelector(`#${prefix}_container`) for (let personId = 1; personId <= nbPersons; personId++) { removePerson(prefix) } } function addRole(personPrefix) { const roleButtonGroup = document.querySelector(`#${personPrefix}_role_add`); const roleIndexNode = document.querySelector(`#${personPrefix}_role_index`); const roleIndex = parseInt(roleIndexNode.value, 10); const ul = document.createElement("ul") ul.classList.add("role"); ul.id = `${personPrefix}_role_${roleIndex}`; ul.innerHTML = `