diff --git a/src/renderer/src/progress/index.js b/src/renderer/src/progress/index.js index 8e39c7e97..212954f47 100644 --- a/src/renderer/src/progress/index.js +++ b/src/renderer/src/progress/index.js @@ -14,6 +14,8 @@ import { merge } from "../stories/pages/utils.js"; import { updateAppProgress, updateFile } from "./update.js"; import { updateURLParams } from "../../utils/url.js"; +import * as operations from "./operations"; + export * from "./update"; class GlobalAppConfig { @@ -122,24 +124,7 @@ export const remove = async (name) => { focusCancel: true, }); - if (result.isConfirmed) { - //Get the path of the progress file to delete - const progressFilePathToDelete = joinPath(guidedProgressFilePath, name + ".json"); - - //delete the progress file - if (fs) fs.unlinkSync(progressFilePathToDelete); - else localStorage.removeItem(progressFilePathToDelete); - - if (fs) { - // delete default stub location - fs.rmSync(joinPath(stubSaveFolderPath, name), { recursive: true, force: true }); - - // delete default conversion location - fs.rmSync(joinPath(conversionSaveFolderPath, name), { recursive: true, force: true }); - } - - return true; - } + if (result.isConfirmed) return operations.remove(name); return false; }; diff --git a/src/renderer/src/progress/operations.js b/src/renderer/src/progress/operations.js new file mode 100644 index 000000000..20ec92b2c --- /dev/null +++ b/src/renderer/src/progress/operations.js @@ -0,0 +1,22 @@ +import { joinPath } from "../globals"; +import { conversionSaveFolderPath, guidedProgressFilePath, stubSaveFolderPath } from "../dependencies/simple"; +import { fs } from "../electron"; + +export const remove = (name) => { + //Get the path of the progress file to delete + const progressFilePathToDelete = joinPath(guidedProgressFilePath, name + ".json"); + + //delete the progress file + if (fs) fs.unlinkSync(progressFilePathToDelete); + else localStorage.removeItem(progressFilePathToDelete); + + if (fs) { + // delete default stub location + fs.rmSync(joinPath(stubSaveFolderPath, name), { recursive: true, force: true }); + + // delete default conversion location + fs.rmSync(joinPath(conversionSaveFolderPath, name), { recursive: true, force: true }); + } + + return true; +}; diff --git a/src/renderer/src/progress/update.js b/src/renderer/src/progress/update.js index 8756db83a..2eafc52a3 100644 --- a/src/renderer/src/progress/update.js +++ b/src/renderer/src/progress/update.js @@ -2,15 +2,15 @@ import { updateURLParams } from "../../utils/url.js"; import { guidedProgressFilePath } from "../dependencies/simple.js"; import { fs } from "../electron/index.js"; import { joinPath } from "../globals.js"; -import { get } from "./index.js"; +import { get, hasEntry } from "./index.js"; -export const update = (newDatasetName, previousDatasetName) => { - //If updataing the dataset, update the old banner image path with a new one +export const rename = (newDatasetName, previousDatasetName) => { + //If updating the dataset, update the old banner image path with a new one if (previousDatasetName) { - if (previousDatasetName === newDatasetName) return "No changes made to dataset name"; + if (previousDatasetName === newDatasetName) return; if (hasEntry(newDatasetName)) - throw new Error("An existing progress file already exists with that name. Please choose a different name."); + throw new Error("An existing project already exists with that name. Please choose a different name."); // update old progress file with new dataset name const oldProgressFilePath = `${guidedProgressFilePath}/${previousDatasetName}.json`; @@ -20,10 +20,9 @@ export const update = (newDatasetName, previousDatasetName) => { localStorage.setItem(newProgressFilePath, localStorage.getItem(oldProgressFilePath)); localStorage.removeItem(oldProgressFilePath); } - - return "Dataset name updated"; - } else throw new Error("No previous dataset name provided"); + } else throw new Error("No previous project name provided"); }; + export const updateAppProgress = ( pageId, dataOrProjectName = {}, diff --git a/src/renderer/src/stories/FileSystemSelector.js b/src/renderer/src/stories/FileSystemSelector.js index df79bc57f..fc21050a9 100644 --- a/src/renderer/src/stories/FileSystemSelector.js +++ b/src/renderer/src/stories/FileSystemSelector.js @@ -188,11 +188,7 @@ export class FilesystemSelector extends LitElement { isUpdated = resolved !== this.value; } - if (isUpdated) { - this.value = resolved; - this.#handleFiles(this.value); // Notify of the change to the separators - return; - } + if (isUpdated) this.#handleFiles(resolved); // Notify of the change to the separators const resolvedValueDisplay = isArray ? len > 1 diff --git a/src/renderer/src/stories/JSONSchemaForm.js b/src/renderer/src/stories/JSONSchemaForm.js index e1902a2a3..f8c82241a 100644 --- a/src/renderer/src/stories/JSONSchemaForm.js +++ b/src/renderer/src/stories/JSONSchemaForm.js @@ -178,7 +178,7 @@ export class JSONSchemaForm extends LitElement { this.identifier = props.identifier; this.mode = props.mode ?? "default"; this.schema = props.schema ?? {}; - this.results = props.results ?? {}; + this.results = (props.base ? structuredClone(props.results) : props.results) ?? {}; // Deep clone results in nested forms this.globals = props.globals ?? {}; this.ignore = props.ignore ?? []; @@ -254,10 +254,10 @@ export class JSONSchemaForm extends LitElement { // NOTE: Forms with nested forms will handle their own state updates if (!value) { - if (fullPath.length === 1) delete resultParent[name]; + delete resultParent[name]; delete resolvedParent[name]; } else { - if (fullPath.length === 1) resultParent[name] = value; + resultParent[name] = value; resolvedParent[name] = value; } @@ -297,9 +297,9 @@ export class JSONSchemaForm extends LitElement { throw new Error(message); }; - validate = async () => { + validate = async (resolved) => { // Check if any required inputs are missing - const invalidInputs = await this.#validateRequirements(); // get missing required paths + const invalidInputs = await this.#validateRequirements(resolved); // get missing required paths const isValid = !invalidInputs.length; // Print out a detailed error message if any inputs are missing @@ -316,9 +316,9 @@ export class JSONSchemaForm extends LitElement { if (message) this.throw(message); - for (let key in this.#nestedForms) await this.#nestedForms[key].validate(); // Validate nested forms too + for (let key in this.#nestedForms) await this.#nestedForms[key].validate(resolved ? resolved[key] : undefined); // Validate nested forms too try { - for (let key in this.tables) await this.tables[key].validate(); // Validate nested tables too + for (let key in this.tables) await this.tables[key].validate(resolved ? resolved[key] : undefined); // Validate nested tables too } catch (e) { this.throw(e.message); } diff --git a/src/renderer/src/stories/pages/Page.js b/src/renderer/src/stories/pages/Page.js index 49b22071b..1e0fffcef 100644 --- a/src/renderer/src/stories/pages/Page.js +++ b/src/renderer/src/stories/pages/Page.js @@ -64,7 +64,12 @@ export class Page extends LitElement { this.beforeTransition(); // Otherwise note unsaved updates if present - if (this.unsavedUpdates || ("states" in this.info && !this.info.states.saved)) { + if ( + this.unsavedUpdates || + ("states" in this.info && + transition === 1 && // Only ensure save for standard forward progression + !this.info.states.saved) + ) { if (transition === 1) await this.save(); // Save before a single forward transition else { Swal.fire({ diff --git a/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js b/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js index eacd86b3a..dd9c9f872 100644 --- a/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js +++ b/src/renderer/src/stories/pages/guided-mode/data/GuidedSourceData.js @@ -1,5 +1,3 @@ -import { html } from "lit"; - import Swal from "sweetalert2"; import { isStorybook } from "../../../../dependencies/globals.js"; import { JSONSchemaForm } from "../../../JSONSchemaForm.js"; diff --git a/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js b/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js index e39b8fe4e..368474643 100644 --- a/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js +++ b/src/renderer/src/stories/pages/guided-mode/setup/GuidedNewDatasetInfo.js @@ -1,5 +1,5 @@ import { html } from "lit"; -import { global, hasEntry, update } from "../../../../progress/index.js"; +import { global, hasEntry, rename } from "../../../../progress/index.js"; import { JSONSchemaForm } from "../../../JSONSchemaForm.js"; import { Page } from "../../Page.js"; import { validateOnChange } from "../../../../validation/index.js"; @@ -50,7 +50,7 @@ export class GuidedNewDatasetPage extends Page { // Update existing progress file if (globalState.initialized) { try { - const res = update(name, globalState.name); + const res = rename(name, globalState.name); if (typeof res === "string") this.notify(res); if (res === false) return; } catch (e) { @@ -61,7 +61,7 @@ export class GuidedNewDatasetPage extends Page { const has = await hasEntry(name); if (has) { this.notify( - "An existing progress file already exists with that name. Please choose a different name.", + "An existing project already exists with that name. Please choose a different name.", "error" ); return; diff --git a/tests/metadata.test.ts b/tests/metadata.test.ts index 2d1292778..5932395d9 100644 --- a/tests/metadata.test.ts +++ b/tests/metadata.test.ts @@ -136,7 +136,6 @@ test('inter-table updates are triggered', async () => { test('changes are resolved correctly', async () => { const results = {} - const schema = { properties: { v0: { @@ -148,11 +147,16 @@ test('changes are resolved correctly', async () => { l2: { type: "object", properties: { - v2: { - type: 'string' - } + l3: { + type: "object", + properties: { + v2: { + type: 'string' + } + }, + required: ['v2'] + }, }, - required: ['v2'] }, v1: { type: 'string' @@ -181,13 +185,13 @@ test('changes are resolved correctly', async () => { const input1 = form.getInput(['v0']) const input2 = form.getInput(['l1', 'v1']) - const input3 = form.getInput(['l1', 'l2', 'v2']) + const input3 = form.getInput(['l1', 'l2', 'l3', 'v2']) input1.updateData('test') input2.updateData('test') input3.updateData('test') // Validate that the new structure is correct - await form.validate().then(res => errors = false).catch(e => errors = true) + await form.validate(form.results).then(res => errors = false).catch(e => errors = true) expect(errors).toBe(false) // Is valid }) diff --git a/tests/progress.test.ts b/tests/progress.test.ts index 52f68b634..f00782f73 100644 --- a/tests/progress.test.ts +++ b/tests/progress.test.ts @@ -1,4 +1,27 @@ -import { test } from 'vitest' -import { updateAppProgress } from '../src/renderer/src/progress/update' +import { expect, test } from 'vitest' +import { updateAppProgress, updateFile, rename } from '../src/renderer/src/progress/update' +import { get } from '../src/renderer/src/progress' +import { remove } from '../src/renderer/src/progress/operations' test('updates to app progress do not fail', () => updateAppProgress('/', {})) + +const initialName = '.progressTestPipelineName' +const renameName = initialName + 2 +const info = { random: Math.random() } + +// Remove before tests +remove(initialName) +remove(renameName) + +// create pipeline +test('pipeline creation works', () => { + updateFile(initialName, () => info) + const result = get(initialName) + expect(result.random).toEqual(info.random) // NOTE: Result has an extra lastModified field +}) + +// rename pipeline +test('pipeline renaming works', () => rename(renameName, initialName)) + +// delete pipeline +test('pipeline deletion works', () => remove(renameName))