From fbfff08f1344fd0ae152f760c8247eef4fd691a2 Mon Sep 17 00:00:00 2001 From: Sam Wray Date: Fri, 7 Apr 2023 20:21:42 +0100 Subject: [PATCH 01/17] fix: move maximize function to keep main window hidden until ready (#839) --- src/background/window-prefs.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/background/window-prefs.js b/src/background/window-prefs.js index 43af0ed75..0109cf5b7 100644 --- a/src/background/window-prefs.js +++ b/src/background/window-prefs.js @@ -63,6 +63,12 @@ const windowPrefs = { unique: true, beforeCreate() { + dialog.showMessageBoxSync({ + message: JSON.stringify({ + isDevelopment, + NODE_ENV: process.env.NODE_ENV + }) + }); const { width, height } = screen.getPrimaryDisplay().workAreaSize; return { @@ -211,16 +217,13 @@ const windowPrefs = { unique: true, async create(window) { - windows["mainWindow"].maximize(); - ipcMain.on("modv-ready", () => { try { window.close(); } catch (e) { console.error(e); } - - windows["mainWindow"].show(); + windows["mainWindow"].maximize(); }); } } From aa4a3d54fa55a7fd170f5678bc14a2e64851a116 Mon Sep 17 00:00:00 2001 From: Sam Wray Date: Fri, 7 Apr 2023 20:21:57 +0100 Subject: [PATCH 02/17] fix: restores tween data correctly when focusing other inputs (#837) --- src/components/Controls/TweenControl.vue | 48 +++++++++++++++++--- src/components/InputLinkComponents/Tween.vue | 12 ++++- 2 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/components/Controls/TweenControl.vue b/src/components/Controls/TweenControl.vue index fd7c776e2..33630630d 100644 --- a/src/components/Controls/TweenControl.vue +++ b/src/components/Controls/TweenControl.vue @@ -10,7 +10,7 @@ + + @@ -93,6 +93,7 @@ import Vec3Control from "./Controls/Vec3Control"; import Vec4Control from "./Controls/Vec4Control"; import hasLink from "./mixins/has-input-link"; import inputIsFocused from "./mixins/input-is-focused"; +import Select from "./inputs/Select.vue"; export default { mixins: [hasLink, inputIsFocused], @@ -137,7 +138,8 @@ export default { FontControl, ColorControl, Vec3Control, - Vec4Control + Vec4Control, + Select }, data() { diff --git a/src/components/Controls/TextureControl.vue b/src/components/Controls/TextureControl.vue index ffd007c71..a69495198 100644 --- a/src/components/Controls/TextureControl.vue +++ b/src/components/Controls/TextureControl.vue @@ -244,4 +244,8 @@ export default { }; - + From a4d2d0b5fb35973ea453269edb424c7b317dda56 Mon Sep 17 00:00:00 2001 From: Sam Wray Date: Sun, 7 May 2023 15:33:47 +0100 Subject: [PATCH 12/17] fix(background-window): fixes macos window creation on dock icon click (#850) --- src/background/background.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/background/background.js b/src/background/background.js index 72b795ba5..b932e477e 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -37,7 +37,7 @@ app.on("window-all-closed", () => { app.on("activate", async () => { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. - createWindow("mainWindow"); + createWindow({ windowName: "mainWindow" }); }); // https://stackoverflow.com/a/66673831 From 2250fa228f7f79a2df11e75039cf23d0b78885d3 Mon Sep 17 00:00:00 2001 From: Sam Wray Date: Thu, 25 May 2023 10:16:05 +0100 Subject: [PATCH 13/17] feat(save/saveas): adds save menu item and updates save as to remember path (#858) * feat(save/saveas): adds save menu item and updates save as to remember path * fix: only set listener once per window creation * fix: move relevant lines into try/catch * build: enable babel transformation of background process * feat(save/saveas): enable save item to open save-as dialog on first save --- src/background/menu-bar.js | 96 ++++++++++++++++++++++++++------------ src/background/windows.js | 4 +- vue.config.js | 10 ++++ yarn.lock | 5 ++ 4 files changed, 82 insertions(+), 33 deletions(-) diff --git a/src/background/menu-bar.js b/src/background/menu-bar.js index f457caf08..f42529193 100644 --- a/src/background/menu-bar.js +++ b/src/background/menu-bar.js @@ -1,4 +1,5 @@ import fs from "fs"; +import path from "path"; import { dialog, shell, app, ipcMain, Menu } from "electron"; import { windows } from "./windows"; import { openFile } from "./open-file"; @@ -7,6 +8,54 @@ import { projectNames, setCurrentProject, currentProject } from "./projects"; const isMac = process.platform === "darwin"; +let lastFileSavedPath = null; + +async function save(filePath) { + let result; + if (!filePath) { + result = await dialog.showSaveDialog(windows["mainWindow"], { + defaultPath: lastFileSavedPath || "preset.json", + filters: [{ name: "Presets", extensions: ["json"] }] + }); + + if (result.canceled) { + return; + } + } + + try { + await writePresetToFile(filePath ?? result.filePath); + lastFileSavedPath = path.resolve(filePath ?? result.filePath); + updateMenu(); + } catch (e) { + console.error(e); + } +} + +async function writePresetToFile(filePath) { + ipcMain.once("preset-data", async (_, presetData) => { + try { + await fs.promises.writeFile(filePath, presetData); + } catch (e) { + dialog.showMessageBox(windows["mainWindow"], { + type: "error", + message: "Could not save preset to file", + detail: e.toString() + }); + } + }); + + try { + windows["mainWindow"].webContents.send("generate-preset"); + } catch (e) { + dialog.showMessageBox(windows["mainWindow"], { + type: "error", + message: "Could not generate preset", + detail: e.toString() + }); + } +} + export function generateMenuTemplate() { const mediaManager = getMediaManager(); @@ -70,38 +119,16 @@ export function generateMenuTemplate() { { type: "separator" }, { label: "Save Preset", + accelerator: "CmdOrCtrl+S", + async click() { + save(lastFileSavedPath); + } + }, + { + label: "Save Preset As…", accelerator: "CmdOrCtrl+Shift+S", async click() { - const result = await dialog.showSaveDialog(windows["mainWindow"], { - defaultPath: "preset.json", - filters: [{ name: "Presets", extensions: ["json"] }] - }); - - if (result.canceled) { - return; - } - - ipcMain.once("preset-data", async (event, presetData) => { - try { - await fs.promises.writeFile(result.filePath, presetData); - } catch (e) { - dialog.showMessageBox(windows["mainWindow"], { - type: "error", - message: "Could not save preset to file", - detail: e.toString() - }); - } - }); - - try { - windows["mainWindow"].webContents.send("generate-preset"); - } catch (e) { - dialog.showMessageBox(windows["mainWindow"], { - type: "error", - message: "Could not generate preset", - detail: e.toString() - }); - } + save(); } }, @@ -239,7 +266,14 @@ export function generateMenuTemplate() { ]; } -export function updateMenu() { +export function updateMenu(setWindowListener) { + if (setWindowListener) { + windows["mainWindow"].on("ready-to-show", () => { + lastFileSavedPath = null; + updateMenu(); + }); + } + const menu = Menu.buildFromTemplate(generateMenuTemplate()); Menu.setApplicationMenu(menu); } diff --git a/src/background/windows.js b/src/background/windows.js index 7ea6f9e07..d23e911e0 100644 --- a/src/background/windows.js +++ b/src/background/windows.js @@ -7,8 +7,6 @@ import { windowPrefs } from "./window-prefs"; const windows = {}; function createWindow({ windowName, options = {} }, event) { - updateMenu(); - if (windowPrefs[windowName].unique && windows[windowName]) { windows[windowName].focus(); windows[windowName].show(); @@ -36,6 +34,8 @@ function createWindow({ windowName, options = {} }, event) { ...options }); + updateMenu(true); + if (typeof windowPrefs[windowName].create === "function") { windowPrefs[windowName].create(windows[windowName]); } diff --git a/vue.config.js b/vue.config.js index 70eb29621..c8333b9ff 100644 --- a/vue.config.js +++ b/vue.config.js @@ -126,6 +126,16 @@ module.exports = { config .plugin("define") .use(DefinePlugin, [{ "process.env.FLUENTFFMPEG_COV": false }]); + + config.module + .rule("babel") + .test(/\.m?js$/) + .exclude.add(/node_modules/) + .end() + .use("babelloader") + .loader("babel-loader", { + presets: [["@babel/preset-env", { targets: "defaults" }]] + }); }, chainWebpackRendererProcess: config => { diff --git a/yarn.lock b/yarn.lock index cd5de06c1..5ad77f4a2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4657,6 +4657,11 @@ csstype@^2.6.8: resolved "https://registry.yarnpkg.com/csstype/-/csstype-2.6.21.tgz#2efb85b7cc55c80017c66a5ad7cbd931fda3a90e" integrity sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w== +csstype@^3.1.0: + version "3.1.2" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" + integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== + current-script-polyfill@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/current-script-polyfill/-/current-script-polyfill-1.0.0.tgz#f31cf7e4f3e218b0726e738ca92a02d3488ef615" From e5f7c7bd8632d1dbd851bafc9aefc082f896361b Mon Sep 17 00:00:00 2001 From: Sam Wray Date: Sun, 11 Jun 2023 07:51:10 +0100 Subject: [PATCH 14/17] feat(expressions): adds `inputValue` as scope item (#859) * feat(expressions): adds `inputValue` as scope item the current control's value is now available on the scope variable `inputValue` in the expression editor, allowing for input feedback fixes #857 * refactor: export swap as named export and remove useless else * feat: add swap to expressions --- src/application/utils/apply-expression.js | 6 +- src/application/worker/index.worker.js | 1 + .../worker/store/modules/common/swap.js | 4 +- .../worker/store/modules/expressions.js | 55 ++++++++++++++----- .../worker/store/modules/groups.js | 2 +- .../worker/store/modules/inputs.js | 9 ++- .../worker/store/modules/modules.js | 7 ++- 7 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/application/utils/apply-expression.js b/src/application/utils/apply-expression.js index 7b6f3d37a..b3cb08566 100644 --- a/src/application/utils/apply-expression.js +++ b/src/application/utils/apply-expression.js @@ -1,3 +1,4 @@ +import get from "lodash.get"; import store from "../worker/store"; export function applyExpression({ value, inputId }) { @@ -5,12 +6,15 @@ export function applyExpression({ value, inputId }) { inputId ); + const input = store.state.inputs.inputs[inputId]; + let dataOut = value; if (expressionAssignment) { const scope = { value: dataOut, - time: Date.now() + time: Date.now(), + inputValue: get(store.state, input.getLocation) }; dataOut = expressionAssignment.func.evaluate(scope); diff --git a/src/application/worker/index.worker.js b/src/application/worker/index.worker.js index 96bcd6c36..1db695556 100644 --- a/src/application/worker/index.worker.js +++ b/src/application/worker/index.worker.js @@ -344,6 +344,7 @@ async function start() { store.commit("groups/SWAP"); store.commit("modules/SWAP"); store.commit("inputs/SWAP"); + store.commit("expressions/SWAP"); return; } diff --git a/src/application/worker/store/modules/common/swap.js b/src/application/worker/store/modules/common/swap.js index 667aa3674..9eed32aa5 100644 --- a/src/application/worker/store/modules/common/swap.js +++ b/src/application/worker/store/modules/common/swap.js @@ -7,7 +7,7 @@ import Vue from "vue"; * The idea is that this makes loading presets smooth and the end user will not see any * glitches in the render loop. */ -export default function SWAP(swap, getDefault, sharedPropertyRestrictions) { +export function SWAP(swap, getDefault, sharedPropertyRestrictions) { return function(state) { const stateKeys = Object.keys(state); @@ -82,8 +82,6 @@ export default function SWAP(swap, getDefault, sharedPropertyRestrictions) { } } }); - } else { - Object.assign(swap, getDefault()); } Object.assign(swap, getDefault()); diff --git a/src/application/worker/store/modules/expressions.js b/src/application/worker/store/modules/expressions.js index 6bfc2fbe3..cfca79b47 100644 --- a/src/application/worker/store/modules/expressions.js +++ b/src/application/worker/store/modules/expressions.js @@ -1,9 +1,16 @@ +import get from "lodash.get"; import { v4 as uuidv4 } from "uuid"; +import { SWAP } from "./common/swap"; const math = require("mathjs"); -const state = { - assignments: {} -}; +function getDefaultState() { + return { + assignments: {} + }; +} + +const state = getDefaultState(); +const swap = getDefaultState(); // getters const getters = { @@ -14,8 +21,8 @@ const getters = { } }; -function compileExpression(expression) { - const scope = { value: 0, time: 0 }; +function compileExpression(expression, scopeItems = {}) { + const scope = { value: 0, time: 0, ...scopeItems }; let newFunction; try { @@ -32,7 +39,10 @@ function compileExpression(expression) { // actions const actions = { - create({ commit }, { expression = "value", id, inputId }) { + create( + { rootState, commit }, + { expression = "value", id, inputId, writeToSwap } + ) { if (!inputId) { throw new Error("Input ID required"); } @@ -43,7 +53,15 @@ const actions = { const expressionId = id || uuidv4(); - const func = compileExpression(expression); + const input = rootState.inputs.inputs[inputId]; + + const func = compileExpression(expression, { + // We currrently have no way of interacting with swap state. + // This would be something to fix in the future, maybe use an entire store + // for swap, or write a more specific mechanism to look up values in swap + // state. + inputValue: writeToSwap ? 0 : get(rootState, input.getLocation) + }); if (!func) { throw new Error("Unable to compile Expression"); @@ -56,12 +74,12 @@ const actions = { expression }; - commit("ADD_EXPRESSION", { assignment }); + commit("ADD_EXPRESSION", { assignment, writeToSwap }); return expressionId; }, - update({ commit }, { id, expression = "value" }) { + update({ rootState, commit }, { id, expression = "value", writeToSwap }) { if (!id) { throw new Error("Expression ID required"); } @@ -77,7 +95,11 @@ const actions = { return null; } - const func = compileExpression(expression); + const input = rootState.inputs.inputs[existingExpression.inputId]; + + const func = compileExpression(expression, { + inputValue: get(rootState, input.getLocation) + }); if (!func) { throw new Error("Unable to compile Expression"); @@ -86,7 +108,7 @@ const actions = { existingExpression.func = func; existingExpression.expression = expression; - commit("ADD_EXPRESSION", { assignment: existingExpression }); + commit("ADD_EXPRESSION", { assignment: existingExpression, writeToSwap }); return existingExpression.id; }, @@ -103,20 +125,23 @@ const actions = { for (let i = 0, len = assignments.length; i < len; i++) { const assignment = assignments[i]; - await dispatch("create", assignment); + await dispatch("create", { ...assignment, writeToSwap: true }); } } }; // mutations const mutations = { - ADD_EXPRESSION(state, { assignment }) { - state.assignments[assignment.id] = assignment; + ADD_EXPRESSION(state, { assignment, writeToSwap = false }) { + const writeTo = writeToSwap ? swap : state; + writeTo.assignments[assignment.id] = assignment; }, REMOVE_EXPRESSION(state, { id }) { delete state.assignments[id]; - } + }, + + SWAP: SWAP(swap, getDefaultState) }; export default { diff --git a/src/application/worker/store/modules/groups.js b/src/application/worker/store/modules/groups.js index db7698a62..d480ecb29 100644 --- a/src/application/worker/store/modules/groups.js +++ b/src/application/worker/store/modules/groups.js @@ -1,4 +1,4 @@ -import SWAP from "./common/swap"; +import { SWAP } from "./common/swap"; import store from "../"; import constants, { GROUP_DISABLED } from "../../../constants"; import { v4 as uuidv4 } from "uuid"; diff --git a/src/application/worker/store/modules/inputs.js b/src/application/worker/store/modules/inputs.js index 62ebf43e8..bc4dd8fed 100644 --- a/src/application/worker/store/modules/inputs.js +++ b/src/application/worker/store/modules/inputs.js @@ -1,6 +1,6 @@ import Vue from "vue"; import { v4 as uuidv4 } from "uuid"; -import SWAP from "./common/swap"; +import { SWAP } from "./common/swap"; /** * InputLinkType enum string values. @@ -99,8 +99,11 @@ const actions = { commit("SET_FOCUSED_INPUT", { id: null, title: null }); }, - addInput({ commit }, { type, location, data, id = uuidv4(), writeToSwap }) { - const input = { type, location, data, id }; + addInput( + { commit }, + { type, getLocation, location, data, id = uuidv4(), writeToSwap } + ) { + const input = { type, getLocation, location, data, id }; commit("ADD_INPUT", { input, writeToSwap }); return input; }, diff --git a/src/application/worker/store/modules/modules.js b/src/application/worker/store/modules/modules.js index a5c30a66e..580ed463d 100644 --- a/src/application/worker/store/modules/modules.js +++ b/src/application/worker/store/modules/modules.js @@ -1,5 +1,5 @@ import Vue from "vue"; -import SWAP from "./common/swap"; +import { SWAP } from "./common/swap"; import getNextName from "../../../utils/get-next-name"; import getPropDefault from "../../../utils/get-prop-default"; import store from ".."; @@ -80,6 +80,7 @@ async function initialiseModuleProperties( ) { const inputBind = await store.dispatch("inputs/addInput", { type: "action", + getLocation: `modules.active["${module.$id}"].props["${propKey}"]`, location: "modules/updateProp", data: { moduleId: module.$id, prop: propKey }, writeToSwap @@ -96,6 +97,7 @@ async function initialiseModuleProperties( const key = dataTypeInputsKeys[i]; await store.dispatch("inputs/addInput", { type: "action", + getLocation: `modules.active["${module.$id}"].props["${propKey}"]["${key}"]`, location: "modules/updateProp", data: { moduleId: module.$id, @@ -288,6 +290,7 @@ const actions = { if (!moduleMeta.isGallery) { const alphaInputBind = await store.dispatch("inputs/addInput", { type: "action", + getLocation: `modules.active["${module.$id}"].meta.alpha`, location: "modules/updateMeta", data: { id: module.$id, metaKey: "alpha" } }); @@ -296,6 +299,7 @@ const actions = { const enabledInputBind = await store.dispatch("inputs/addInput", { type: "action", + getLocation: `modules.active["${module.$id}"].meta.enabled`, location: "modules/updateMeta", data: { id: module.$id, metaKey: "enabled" } }); @@ -304,6 +308,7 @@ const actions = { const coInputBind = await store.dispatch("inputs/addInput", { type: "action", + getLocation: `modules.active["${module.$id}"].meta.compositeOperation`, location: "modules/updateMeta", data: { moduleId: module.$id, metaKey: "compositeOperation" } }); From 2cad1e060a542e5a128b338e0e2ba9620e8a24fd Mon Sep 17 00:00:00 2001 From: Sam Wray Date: Sun, 11 Jun 2023 07:54:44 +0100 Subject: [PATCH 15/17] test: add playwright for e2e tests (#828) * test: add playwright for e2e tests fix :#688 * test: add gallery search/module addition * test: fix gallery item locator * test: add waitUntilModVReady function * test: refactor waitUntilModVReady to reduce promise creation * test: refactor waitUntilModVReady to remove useless else * test: refactor waitUntilModVReady to remove useless if * test: tidy up * test: add preset generation test * test: add evaluateWorkerState * test: add new group test * test: fix typo * test: add test:e2e script to package.json * test: split group tests into their own file * test: add group enabled toggle test * test: improve backspace removes focused group * test: add group clearing and pipeline tests * test: add inheritance test * test: add alpha state test * test: add blend mode test * refactor: remove console.log * test: add group name test * test: consolidate name locators * test: add group rearrangement test * test: tidy up * test: fix scope * test: add input link test * test: split up test files to avoid worker overlap * test: remove unneeded reload and wait * test: remove useless code * test: remove unused deps * fix: update title to name and fix keyboard event * test: significantly speed up tests on slower machines * test: remove page.waitForTimeout --- package.json | 7 +- playwright.config.js | 9 ++ src/application/worker/index.worker.js | 3 + src/background/background.js | 8 +- src/background/window-prefs.js | 11 +- src/components/ActiveModule.vue | 9 +- src/components/Control.vue | 1 + src/components/Group.vue | 53 ++++--- src/components/Groups.vue | 6 +- src/components/InputConfig.vue | 4 +- src/components/ModuleInspector.vue | 10 +- src/main.js | 6 + tests/e2e/extentions/index.js | 3 + tests/e2e/extentions/toBeJSON.js | 15 ++ tests/e2e/main.spec.js | 94 ++++++++++++ tests/e2e/pageObjectModel/gallery.js | 56 +++++++ tests/e2e/pageObjectModel/groups.js | 81 ++++++++++ tests/e2e/pageObjectModel/index.js | 145 ++++++++++++++++++ tests/e2e/pageObjectModel/modules.js | 54 +++++++ tests/e2e/pageObjectModel/tabs.js | 12 ++ .../defaultGroupIsFocusedByDefault.spec.js | 19 +++ .../general/renderersAreRegistered.spec.js | 10 ++ .../spec/general/rendersTheMainWindow.spec.js | 9 ++ .../searchGalleryAndAddModuleToGroup.spec.js | 34 ++++ .../spec/groups/alphaStateCanBeSet.spec.js | 26 ++++ .../backspaceRemovesFocusedGroup.spec.js | 41 +++++ .../groups/blendModeStateCanBeSet.spec.js | 29 ++++ ...aringStateCanBeToggledBetween0and1.spec.js | 22 +++ .../spec/groups/createsADefaultGroup.spec.js | 11 ++ ...bledStateCanBeToggledBetween01and2.spec.js | 48 ++++++ .../spec/groups/groupNameCanBeChanged.spec.js | 26 ++++ .../spec/groups/groupsCanBeRearranged.spec.js | 39 +++++ ...inheritAndInheritFromStateCanBeSet.spec.js | 41 +++++ .../newGroupButtonCreatesANewGroup.spec.js | 15 ++ ...elineStateCanBeToggledBetween0and1.spec.js | 22 +++ ...tConfigUpdatesWhenControlIsFocused.spec.js | 29 ++++ .../generatesAPresetWithExpectedKeys.spec.js | 21 +++ tests/e2e/utils/setRangeValue.js | 7 + yarn.lock | 46 ++++++ 39 files changed, 1044 insertions(+), 38 deletions(-) create mode 100644 playwright.config.js create mode 100644 tests/e2e/extentions/index.js create mode 100644 tests/e2e/extentions/toBeJSON.js create mode 100644 tests/e2e/main.spec.js create mode 100644 tests/e2e/pageObjectModel/gallery.js create mode 100644 tests/e2e/pageObjectModel/groups.js create mode 100644 tests/e2e/pageObjectModel/index.js create mode 100644 tests/e2e/pageObjectModel/modules.js create mode 100644 tests/e2e/pageObjectModel/tabs.js create mode 100644 tests/e2e/spec/general/defaultGroupIsFocusedByDefault.spec.js create mode 100644 tests/e2e/spec/general/renderersAreRegistered.spec.js create mode 100644 tests/e2e/spec/general/rendersTheMainWindow.spec.js create mode 100644 tests/e2e/spec/general/searchGalleryAndAddModuleToGroup.spec.js create mode 100644 tests/e2e/spec/groups/alphaStateCanBeSet.spec.js create mode 100644 tests/e2e/spec/groups/backspaceRemovesFocusedGroup.spec.js create mode 100644 tests/e2e/spec/groups/blendModeStateCanBeSet.spec.js create mode 100644 tests/e2e/spec/groups/clearingStateCanBeToggledBetween0and1.spec.js create mode 100644 tests/e2e/spec/groups/createsADefaultGroup.spec.js create mode 100644 tests/e2e/spec/groups/enabledStateCanBeToggledBetween01and2.spec.js create mode 100644 tests/e2e/spec/groups/groupNameCanBeChanged.spec.js create mode 100644 tests/e2e/spec/groups/groupsCanBeRearranged.spec.js create mode 100644 tests/e2e/spec/groups/inheritAndInheritFromStateCanBeSet.spec.js create mode 100644 tests/e2e/spec/groups/newGroupButtonCreatesANewGroup.spec.js create mode 100644 tests/e2e/spec/groups/pipelineStateCanBeToggledBetween0and1.spec.js create mode 100644 tests/e2e/spec/inputLinks/inputConfigUpdatesWhenControlIsFocused.spec.js create mode 100644 tests/e2e/spec/presets/generatesAPresetWithExpectedKeys.spec.js create mode 100644 tests/e2e/utils/setRangeValue.js diff --git a/package.json b/package.json index 675fb5dd7..27281c5f4 100644 --- a/package.json +++ b/package.json @@ -20,7 +20,8 @@ "electron:build": "vue-cli-service electron:build", "electron:serve": "vue-cli-service electron:serve", "postinstall": "electron-builder install-app-deps && patch-package", - "postuninstall": "electron-builder install-app-deps" + "postuninstall": "electron-builder install-app-deps", + "test:e2e": "npx playwright test" }, "main": "background.js", "dependencies": { @@ -70,6 +71,7 @@ "@babel/core": "^7.0.0-0", "@babel/plugin-proposal-nullish-coalescing-operator": "^7.16.0", "@babel/plugin-proposal-optional-chaining": "^7.16.0", + "@playwright/test": "^1.31.2", "@semantic-release/git": "^9.0.0", "@vue/cli-plugin-babel": "^4.5.15", "@vue/cli-plugin-eslint": "^3.12.1", @@ -81,11 +83,14 @@ "electron": "^23.1.2", "electron-builder": "^22.9.1", "electron-notarize": "^1.2.2", + "electron-playwright-helpers": "^1.5.3", "eslint": "^5.16.0", "eslint-plugin-no-for-each": "^0.1.14", "eslint-plugin-vue": "^5.2.3", "lint-staged": "^8.2.1", "node-loader": "^0.6.0", + "playwright": "^1.31.2", + "playwright-core": "^1.31.2", "sass-loader": "^7.3.1", "text-loader": "0.0.1", "vue-cli-plugin-electron-builder": "^2.0.0", diff --git a/playwright.config.js b/playwright.config.js new file mode 100644 index 000000000..691eac234 --- /dev/null +++ b/playwright.config.js @@ -0,0 +1,9 @@ +import { defineConfig, expect } from "@playwright/test"; +import extensions from "./tests/e2e/extentions"; + +expect.extend(extensions); + +export default defineConfig({ + testDir: "./tests/e2e/spec", + workers: process.env.CI ? 1 : 2 +}); diff --git a/src/application/worker/index.worker.js b/src/application/worker/index.worker.js index 1db695556..9b52ead43 100644 --- a/src/application/worker/index.worker.js +++ b/src/application/worker/index.worker.js @@ -11,6 +11,9 @@ async function start() { const grabCanvasPlugin = require("../plugins/grab-canvas").default; const get = require("lodash.get"); + // For Playwright + self._get = get; + const { tick: frameTick } = require("./frame-counter"); const { getFeatures, setFeatures } = require("./audio-features"); // const featureAssignmentPlugin = require("../plugins/feature-assignment"); diff --git a/src/background/background.js b/src/background/background.js index b932e477e..592a2562c 100644 --- a/src/background/background.js +++ b/src/background/background.js @@ -1,4 +1,4 @@ -import { app, protocol } from "electron"; +import { app, ipcMain, protocol } from "electron"; import { APP_SCHEME } from "./background-constants"; import { getMediaManager } from "./media-manager"; import { openFile } from "./open-file"; @@ -73,8 +73,10 @@ app.on("ready", async () => { ); createWindow({ windowName: "mainWindow" }); - createWindow({ windowName: "splashScreen" }); - createWindow({ windowName: "colorPicker", options: { show: false } }); + ipcMain.once("main-window-created", () => { + createWindow({ windowName: "splashScreen" }); + createWindow({ windowName: "colorPicker", options: { show: false } }); + }); }); // Exit cleanly on request from parent process in development mode. diff --git a/src/background/window-prefs.js b/src/background/window-prefs.js index 56f04fb2c..1471df3f1 100644 --- a/src/background/window-prefs.js +++ b/src/background/window-prefs.js @@ -8,8 +8,10 @@ import { closeWindow, createWindow, windows } from "./windows"; import { updateMenu } from "./menu-bar"; import { getMediaManager } from "./media-manager"; -const isDevelopment = process.env.NODE_ENV !== "production"; +const isDevelopment = process.env.NODE_ENV === "development"; +const isTest = process.env.CI === "e2e"; let shouldCloseMainWindowAndQuit = false; +let modVReady = false; const windowPrefs = { colorPicker: { @@ -65,6 +67,8 @@ const windowPrefs = { beforeCreate() { const { width, height } = screen.getPrimaryDisplay().workAreaSize; + modVReady = false; + return { options: { width, @@ -76,6 +80,8 @@ const windowPrefs = { async create(window) { require("@electron/remote/main").enable(window.webContents); + ipcMain.handle("is-modv-ready", () => modVReady); + // Configure child windows to open without a menubar (windows/linux) window.webContents.on( "new-window", @@ -114,6 +120,7 @@ const windowPrefs = { }); ipcMain.on("modv-ready", () => { + modVReady = true; mm.start(); }); @@ -144,7 +151,7 @@ const windowPrefs = { window.webContents.send("input-update", message); }); - if (!isDevelopment || process.env.IS_TEST) { + if (!isDevelopment && !isTest) { window.on("close", async e => { if (shouldCloseMainWindowAndQuit) { app.quit(); diff --git a/src/components/ActiveModule.vue b/src/components/ActiveModule.vue index 31c6da4ad..b050e7cb3 100644 --- a/src/components/ActiveModule.vue +++ b/src/components/ActiveModule.vue @@ -6,9 +6,10 @@ @focus="clickActiveModule" ref="activeModule" :class="{ focused }" + :id="`active-module-${id}`" >
@@ -264,7 +265,7 @@ export default { outline: #c4c4c4 2px solid; } -.active-module__title { +.active-module__name { background: #9a9a9a; width: 100%; overflow: hidden; @@ -273,7 +274,7 @@ export default { cursor: grab; } -.active-module__title.grabbing { +.active-module__name.grabbing { cursor: grabbing; } @@ -285,7 +286,7 @@ export default { } .active-module__controls grid, -.active-module__title { +.active-module__name { box-sizing: border-box; padding: 0 4px; } diff --git a/src/components/Control.vue b/src/components/Control.vue index b33fca0c0..614446126 100644 --- a/src/components/Control.vue +++ b/src/components/Control.vue @@ -3,6 +3,7 @@ columns="4" @mousedown="focusInput" :class="{ 'has-link': hasLink, focused: inputIsFocused }" + :id="`module-control-${inputId}`" > diff --git a/src/components/Group.vue b/src/components/Group.vue index ee8eea27f..30f0804ec 100644 --- a/src/components/Group.vue +++ b/src/components/Group.vue @@ -1,6 +1,7 @@ diff --git a/src/components/InputConfig.vue b/src/components/InputConfig.vue index e75b83900..0b84e5b01 100644 --- a/src/components/InputConfig.vue +++ b/src/components/InputConfig.vue @@ -20,7 +20,7 @@
{{ focusedInputTitle }}
{{ focusedInputTitle }}
@@ -196,7 +196,7 @@ grid.borders > c:not(:last-child):not(:first-child) { border-bottom: 1px solid var(--foreground-color-2); } -.title { +.input-config__title { font-size: 24px; } diff --git a/src/components/ModuleInspector.vue b/src/components/ModuleInspector.vue index dceb0afe9..94300c8fd 100644 --- a/src/components/ModuleInspector.vue +++ b/src/components/ModuleInspector.vue @@ -1,12 +1,16 @@