From 2ea812d0616f7873121d2be6a99ef5e583624e7d Mon Sep 17 00:00:00 2001 From: Alexandre Henrique Afonso Campos Date: Tue, 8 Sep 2020 07:51:58 -0300 Subject: [PATCH 01/21] Check signed build before check name --- tnoodle-ui/src/components/VersionInfo.jsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/tnoodle-ui/src/components/VersionInfo.jsx b/tnoodle-ui/src/components/VersionInfo.jsx index 3d324e618..4b90dfbcf 100644 --- a/tnoodle-ui/src/components/VersionInfo.jsx +++ b/tnoodle-ui/src/components/VersionInfo.jsx @@ -38,11 +38,11 @@ const VersionInfo = connect( fetchRunningVersion() .then((response) => response.json()) .then((version) => { - let { runningVersion, officialBuild } = version; + let { runningVersion, signedBuild } = version; this.setState({ ...this.state, runningVersion, - officialBuild, + officialBuild: signedBuild, }); }) .catch((e) => console.error(e)); @@ -59,11 +59,6 @@ const VersionInfo = connect( return null; } - // Best case scenario - if (runningVersion === currentTnoodle.name) { - return null; - } - // Generated version is not an official jar if (!officialBuild) { this.props.updateOfficialZipStatus(false); @@ -78,6 +73,12 @@ const VersionInfo = connect( ); } + // Best case scenario. We place this after the officialBuild + // so we can avoid not official build from being accidentally used. + if (runningVersion === currentTnoodle.name) { + return null; + } + // Running version is not allowed anymore. if (!allowedVersions.includes(runningVersion)) { this.props.updateOfficialZipStatus(false); From 455a078c0167724e56bca98b5b01853ddf4f3ec6 Mon Sep 17 00:00:00 2001 From: Alexandre Henrique Afonso Campos Date: Tue, 8 Sep 2020 07:52:09 -0300 Subject: [PATCH 02/21] Add unit tests for Version Info --- tnoodle-ui/src/components/VersionInfo.test.js | 291 ++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 tnoodle-ui/src/components/VersionInfo.test.js diff --git a/tnoodle-ui/src/components/VersionInfo.test.js b/tnoodle-ui/src/components/VersionInfo.test.js new file mode 100644 index 000000000..8e881cebe --- /dev/null +++ b/tnoodle-ui/src/components/VersionInfo.test.js @@ -0,0 +1,291 @@ +import React from "react"; +import { act } from "react-dom/test-utils"; + +import { render, unmountComponentAtNode } from "react-dom"; + +import { Provider } from "react-redux"; +import store from "../redux/Store"; + +import VersionInfo from "./VersionInfo"; + +const tnoodleApi = require("../api/tnoodle.api"); +const wcaApi = require("../api/wca.api"); + +let container = null; +beforeEach(() => { + // setup a DOM element as a render target + container = document.createElement("div"); + document.body.appendChild(container); +}); + +afterEach(() => { + // cleanup on exiting + unmountComponentAtNode(container); + container.remove(); + container = null; +}); + +it("Current version is the correct one", async () => { + // Define mock objects + const version = { + runningVersion: "TNoodle-WCA-2", + projectName: "TNoodle-WCA", + projectVersion: "2", + signedBuild: true, + signatureKeyBytes: "key", + }; + + const scrambleProgram = { + current: { + name: "TNoodle-WCA-2", + information: + "https://www.worldcubeassociation.org/regulations/scrambles/", + download: + "https://www.worldcubeassociation.org/regulations/scrambles/tnoodle/TNoodle-WCA-2.jar", + }, + allowed: ["TNoodle-WCA-2"], + history: ["TNoodle-WCA-0", "TNoodle-WCA-1", "TNoodle-WCA-2"], + }; + + // Turn on mocking behavior + jest.spyOn(tnoodleApi, "fetchRunningVersion").mockImplementation(() => + Promise.resolve({ + json: () => Promise.resolve(version), + }) + ); + + jest.spyOn(wcaApi, "fetchVersionInfo").mockImplementation(() => + Promise.resolve(scrambleProgram) + ); + + // Render component + await act(async () => { + render( + + + , + container + ); + }); + + // There should be no warning + const alert = container.querySelector(".alert"); + expect(alert).toBe(null); + + // Clear mock + tnoodleApi.fetchRunningVersion.mockRestore(); + wcaApi.fetchVersionInfo.mockRestore(); +}); + +it("Current version is allowed, but it's not the latest one", async () => { + const version = { + runningVersion: "TNoodle-WCA-2", + projectName: "TNoodle-WCA", + projectVersion: "2", + signedBuild: true, + signatureKeyBytes: "key", + }; + + const scrambleProgram = { + current: { + name: "TNoodle-WCA-3", + information: + "https://www.worldcubeassociation.org/regulations/scrambles/", + download: + "https://www.worldcubeassociation.org/regulations/scrambles/tnoodle/TNoodle-WCA-3.jar", + }, + allowed: ["TNoodle-WCA-2", "TNoodle-WCA-3"], + history: [ + "TNoodle-WCA-0", + "TNoodle-WCA-1", + "TNoodle-WCA-2", + "TNoodle-WCA-3", + ], + }; + + jest.spyOn(tnoodleApi, "fetchRunningVersion").mockImplementation(() => + Promise.resolve({ + json: () => Promise.resolve(version), + }) + ); + + jest.spyOn(wcaApi, "fetchVersionInfo").mockImplementation(() => + Promise.resolve(scrambleProgram) + ); + + await act(async () => { + render( + + + , + container + ); + }); + + // The warning should be "your version is ok, but please upgrade" + const alert = container.querySelector(".alert-info"); + expect(alert.textContent).toContain( + "which is still allowed, but you should upgrade to" + ); + + const downloadLink = container.querySelector("a").href; + expect(downloadLink).toBe(scrambleProgram.current.download); + + tnoodleApi.fetchRunningVersion.mockRestore(); + wcaApi.fetchVersionInfo.mockRestore(); +}); + +it("Not official version alert", async () => { + const version = { + runningVersion: "TNoodle-WCA-3", + projectName: "TNoodle-WCA", + projectVersion: "3", + signedBuild: false, // This should trigger an alert, even with currentVersion == runningVersion + signatureKeyBytes: "key", + }; + + const scrambleProgram = { + current: { + name: "TNoodle-WCA-3", + information: + "https://www.worldcubeassociation.org/regulations/scrambles/", + download: + "https://www.worldcubeassociation.org/regulations/scrambles/tnoodle/TNoodle-WCA-3.jar", + }, + allowed: ["TNoodle-WCA-3"], + history: [ + "TNoodle-WCA-0", + "TNoodle-WCA-1", + "TNoodle-WCA-2", + "TNoodle-WCA-3", + ], + }; + + jest.spyOn(tnoodleApi, "fetchRunningVersion").mockImplementation(() => + Promise.resolve({ + json: () => Promise.resolve(version), + }) + ); + + jest.spyOn(wcaApi, "fetchVersionInfo").mockImplementation(() => + Promise.resolve(scrambleProgram) + ); + + await act(async () => { + render( + + + , + container + ); + }); + + // The warning should be "do not use this" + const alert = container.querySelector(".alert-danger"); + expect(alert.textContent).toContain( + "This TNoodle version is not official and scrambles generated with this must not be used in competition." + ); + + tnoodleApi.fetchRunningVersion.mockRestore(); + wcaApi.fetchVersionInfo.mockRestore(); +}); + +it("Not allowed TNoodle version, despite it's official", async () => { + const version = { + runningVersion: "TNoodle-WCA-1", + projectName: "TNoodle-WCA", + projectVersion: "1", + signedBuild: true, + signatureKeyBytes: "key", + }; + + const scrambleProgram = { + current: { + name: "TNoodle-WCA-3", + information: + "https://www.worldcubeassociation.org/regulations/scrambles/", + download: + "https://www.worldcubeassociation.org/regulations/scrambles/tnoodle/TNoodle-WCA-3.jar", + }, + allowed: ["TNoodle-WCA-2", "TNoodle-WCA-3"], + history: [ + "TNoodle-WCA-0", + "TNoodle-WCA-1", + "TNoodle-WCA-2", + "TNoodle-WCA-3", + ], + }; + + jest.spyOn(tnoodleApi, "fetchRunningVersion").mockImplementation(() => + Promise.resolve({ + json: () => Promise.resolve(version), + }) + ); + + jest.spyOn(wcaApi, "fetchVersionInfo").mockImplementation(() => + Promise.resolve(scrambleProgram) + ); + + await act(async () => { + render( + + + , + container + ); + }); + + // The warning should be "do not use this" + const alert = container.querySelector(".alert-danger"); + expect(alert.textContent).toContain("This TNoodle version is not allowed."); + + tnoodleApi.fetchRunningVersion.mockRestore(); + wcaApi.fetchVersionInfo.mockRestore(); +}); + +it("Do not bother the user if we can't be sure", async () => { + const version = {}; + + const scrambleProgram = { + current: { + name: "TNoodle-WCA-3", + information: + "https://www.worldcubeassociation.org/regulations/scrambles/", + download: + "https://www.worldcubeassociation.org/regulations/scrambles/tnoodle/TNoodle-WCA-3.jar", + }, + allowed: ["TNoodle-WCA-2", "TNoodle-WCA-3"], + history: [ + "TNoodle-WCA-0", + "TNoodle-WCA-1", + "TNoodle-WCA-2", + "TNoodle-WCA-3", + ], + }; + + jest.spyOn(tnoodleApi, "fetchRunningVersion").mockImplementation(() => + Promise.resolve({ + json: () => Promise.resolve(version), + }) + ); + + jest.spyOn(wcaApi, "fetchVersionInfo").mockImplementation(() => + Promise.resolve(scrambleProgram) + ); + + await act(async () => { + render( + + + , + container + ); + }); + + // The warning should be "do not use this" + const alert = container.querySelector(".alert"); + expect(alert).toBe(null); + + tnoodleApi.fetchRunningVersion.mockRestore(); + wcaApi.fetchVersionInfo.mockRestore(); +}); From 7becf10297fd4e865a91c4d86dd2e68f4e7f2e2c Mon Sep 17 00:00:00 2001 From: Alexandre Henrique Afonso Campos Date: Tue, 8 Sep 2020 20:40:23 -0300 Subject: [PATCH 03/21] More standard for component did mount --- tnoodle-ui/src/components/EventPickerTable.jsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tnoodle-ui/src/components/EventPickerTable.jsx b/tnoodle-ui/src/components/EventPickerTable.jsx index c83bdc8b5..bdd81a40f 100644 --- a/tnoodle-ui/src/components/EventPickerTable.jsx +++ b/tnoodle-ui/src/components/EventPickerTable.jsx @@ -36,11 +36,11 @@ const EventPickerTable = connect( mapDispatchToProps )( class extends Component { - componentDidMount = function () { + componentDidMount() { this.getFormats(); this.getWcaEvents(); this.getFmcTranslations(); - }; + } getFormats = () => { fetchFormats() From a3416788ff31e96e5885ddd100055489ca223100 Mon Sep 17 00:00:00 2001 From: Alexandre Henrique Afonso Campos Date: Tue, 8 Sep 2020 21:48:33 -0300 Subject: [PATCH 04/21] Dismiss the need of from backend --- tnoodle-ui/src/components/VersionInfo.jsx | 8 ++++++-- tnoodle-ui/src/components/VersionInfo.test.js | 4 ---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tnoodle-ui/src/components/VersionInfo.jsx b/tnoodle-ui/src/components/VersionInfo.jsx index 4b90dfbcf..f6141521a 100644 --- a/tnoodle-ui/src/components/VersionInfo.jsx +++ b/tnoodle-ui/src/components/VersionInfo.jsx @@ -38,10 +38,14 @@ const VersionInfo = connect( fetchRunningVersion() .then((response) => response.json()) .then((version) => { - let { runningVersion, signedBuild } = version; + let { projectName, projectVersion, signedBuild } = version; this.setState({ ...this.state, - runningVersion, + // Running version is based on projectName and projectVersion + runningVersion: + projectVersion != null && projectVersion != null + ? `${projectName}-${projectVersion}` + : "", officialBuild: signedBuild, }); }) diff --git a/tnoodle-ui/src/components/VersionInfo.test.js b/tnoodle-ui/src/components/VersionInfo.test.js index 8e881cebe..af92dc39d 100644 --- a/tnoodle-ui/src/components/VersionInfo.test.js +++ b/tnoodle-ui/src/components/VersionInfo.test.js @@ -28,7 +28,6 @@ afterEach(() => { it("Current version is the correct one", async () => { // Define mock objects const version = { - runningVersion: "TNoodle-WCA-2", projectName: "TNoodle-WCA", projectVersion: "2", signedBuild: true, @@ -79,7 +78,6 @@ it("Current version is the correct one", async () => { it("Current version is allowed, but it's not the latest one", async () => { const version = { - runningVersion: "TNoodle-WCA-2", projectName: "TNoodle-WCA", projectVersion: "2", signedBuild: true, @@ -137,7 +135,6 @@ it("Current version is allowed, but it's not the latest one", async () => { it("Not official version alert", async () => { const version = { - runningVersion: "TNoodle-WCA-3", projectName: "TNoodle-WCA", projectVersion: "3", signedBuild: false, // This should trigger an alert, even with currentVersion == runningVersion @@ -192,7 +189,6 @@ it("Not official version alert", async () => { it("Not allowed TNoodle version, despite it's official", async () => { const version = { - runningVersion: "TNoodle-WCA-1", projectName: "TNoodle-WCA", projectVersion: "1", signedBuild: true, From c4cd8084645127c715293b4379e518da47271e5f Mon Sep 17 00:00:00 2001 From: Alexandre Henrique Afonso Campos Date: Tue, 8 Sep 2020 21:48:50 -0300 Subject: [PATCH 05/21] Add UI tests in travis --- .travis.yml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/.travis.yml b/.travis.yml index bc7874a1e..3d5279314 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,8 +1,16 @@ -language: java -jdk: openjdk8 -sudo: true +jobs: + include: + - language: java + jdk: openjdk8 + sudo: true + script: + - ./gradlew assemble + - ./gradlew check + - ./gradlew buildOfficial -script: - - ./gradlew assemble - - ./gradlew check - - ./gradlew buildOfficial + - language: node_js + node_js: 10 + script: + - cd tnoodle-ui + - npm install + - npm run test From 3728d4a79f14576623f62e8b8de153d631b167d6 Mon Sep 17 00:00:00 2001 From: Alexandre Henrique Afonso Campos Date: Tue, 8 Sep 2020 22:16:14 -0300 Subject: [PATCH 06/21] Limit the number of competition for api integration tests in backend --- .../tnoodle/server/webscrambles/APIIntegrationTest.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/webscrambles/src/test/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/APIIntegrationTest.kt b/webscrambles/src/test/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/APIIntegrationTest.kt index ff8306f2a..6797ae330 100644 --- a/webscrambles/src/test/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/APIIntegrationTest.kt +++ b/webscrambles/src/test/kotlin/org/worldcubeassociation/tnoodle/server/webscrambles/APIIntegrationTest.kt @@ -19,8 +19,9 @@ import kotlin.system.measureTimeMillis object APIIntegrationTest { @Test fun `test that every upcoming competition produces some output without error`() { + val competitionsLimit = 10 val upcomingRaw = URL("https://www.worldcubeassociation.org/api/v0/competitions/").readText() - val upcomingComps = JsonConfig.SERIALIZER.decodeFromString(ListSerializer(UpcomingCompetition.serializer()), upcomingRaw) + val upcomingComps = JsonConfig.SERIALIZER.decodeFromString(ListSerializer(UpcomingCompetition.serializer()), upcomingRaw).take(competitionsLimit) val generationDate = LocalDateTime.now() val testCompCount = upcomingComps.size From e6ae2057c3a72c41dfe5e32e019ca20c145ab779 Mon Sep 17 00:00:00 2001 From: Alexandre Henrique Afonso Campos Date: Tue, 8 Sep 2020 22:52:18 -0300 Subject: [PATCH 07/21] Add coverage to package.json --- tnoodle-ui/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tnoodle-ui/package.json b/tnoodle-ui/package.json index 204d6ad84..83350c671 100644 --- a/tnoodle-ui/package.json +++ b/tnoodle-ui/package.json @@ -29,7 +29,8 @@ "test": "react-scripts test --watchAll --watchAll=false", "eject": "react-scripts eject", "predeploy": "npm run build", - "deploy": "gh-pages -d build" + "deploy": "gh-pages -d build", + "coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls" }, "browserslist": { "production": [ From ce562cb96c464480eccf9f31219e88f4d38906c2 Mon Sep 17 00:00:00 2001 From: Alexandre Henrique Afonso Campos Date: Tue, 8 Sep 2020 22:52:49 -0300 Subject: [PATCH 08/21] Lint package.json --- tnoodle-ui/package.json | 90 ++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/tnoodle-ui/package.json b/tnoodle-ui/package.json index 83350c671..07f205fe8 100644 --- a/tnoodle-ui/package.json +++ b/tnoodle-ui/package.json @@ -1,47 +1,47 @@ { - "name": "tnoodle-ui", - "version": "0.1.0", - "private": true, - "homepage": "http://localhost:2014/scramble", - "proxy": "http://localhost:2014", - "dependencies": { - "@testing-library/jest-dom": "^4.2.4", - "@testing-library/react": "^9.3.2", - "@testing-library/user-event": "^7.1.2", - "bootstrap": "^4.4.1", - "fetch-intercept": "^2.3.1", - "node-sass": "^4.13.1", - "react": "^16.12.0", - "react-bootstrap": "^1.0.0-beta.16", - "react-dom": "^16.12.0", - "react-icons": "^3.10.0", - "react-redux": "^7.1.3", - "react-router-dom": "^5.1.2", - "react-scripts": "3.4.3", - "redux": "^4.0.5" - }, - "eslintConfig": { - "extends": "react-app" - }, - "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", - "test": "react-scripts test --watchAll --watchAll=false", - "eject": "react-scripts eject", - "predeploy": "npm run build", - "deploy": "gh-pages -d build", - "coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls" - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - } + "name": "tnoodle-ui", + "version": "0.1.0", + "private": true, + "homepage": "http://localhost:2014/scramble", + "proxy": "http://localhost:2014", + "dependencies": { + "@testing-library/jest-dom": "^4.2.4", + "@testing-library/react": "^9.3.2", + "@testing-library/user-event": "^7.1.2", + "bootstrap": "^4.4.1", + "fetch-intercept": "^2.3.1", + "node-sass": "^4.13.1", + "react": "^16.12.0", + "react-bootstrap": "^1.0.0-beta.16", + "react-dom": "^16.12.0", + "react-icons": "^3.10.0", + "react-redux": "^7.1.3", + "react-router-dom": "^5.1.2", + "react-scripts": "3.4.3", + "redux": "^4.0.5" + }, + "eslintConfig": { + "extends": "react-app" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --watchAll --watchAll=false", + "eject": "react-scripts eject", + "predeploy": "npm run build", + "deploy": "gh-pages -d build", + "coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls" + }, + "browserslist": { + "production": [ + ">0.2%", + "not dead", + "not op_mini all" + ], + "development": [ + "last 1 chrome version", + "last 1 firefox version", + "last 1 safari version" + ] + } } From ef8e6f3062aa4aedb52494138b194d2ac6a65b2a Mon Sep 17 00:00:00 2001 From: Alexandre Henrique Afonso Campos Date: Tue, 8 Sep 2020 22:55:15 -0300 Subject: [PATCH 09/21] Install coverage dependencies --- tnoodle-ui/package.json | 3 +++ tnoodle-ui/yarn.lock | 23 ++++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/tnoodle-ui/package.json b/tnoodle-ui/package.json index 07f205fe8..62b38142e 100644 --- a/tnoodle-ui/package.json +++ b/tnoodle-ui/package.json @@ -43,5 +43,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "coveralls": "^3.1.0" } } diff --git a/tnoodle-ui/yarn.lock b/tnoodle-ui/yarn.lock index 5075bf0ac..20fcf6e1f 100644 --- a/tnoodle-ui/yarn.lock +++ b/tnoodle-ui/yarn.lock @@ -3415,6 +3415,17 @@ cosmiconfig@^6.0.0: path-type "^4.0.0" yaml "^1.7.2" +coveralls@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-3.1.0.tgz#13c754d5e7a2dd8b44fe5269e21ca394fb4d615b" + integrity sha512-sHxOu2ELzW8/NC1UP5XVLbZDzO4S3VxfFye3XYCznopHy02YjNkHcj5bKaVw2O7hVaBdBjEdQGpie4II1mWhuQ== + dependencies: + js-yaml "^3.13.1" + lcov-parse "^1.0.0" + log-driver "^1.2.7" + minimist "^1.2.5" + request "^2.88.2" + create-ecdh@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.4.tgz#d6e7f4bffa66736085a0762fd3a632684dabcc4e" @@ -6763,6 +6774,11 @@ lazy-cache@^1.0.3: resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" integrity sha1-odePw6UEdMuAhF07O24dpJpEbo4= +lcov-parse@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-1.0.0.tgz#eb0d46b54111ebc561acb4c408ef9363bdc8f7e0" + integrity sha1-6w1GtUER68VhrLTECO+TY73I9+A= + left-pad@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/left-pad/-/left-pad-1.3.0.tgz#5b8a3a7765dfe001261dde915589e782f8c94d1e" @@ -6923,6 +6939,11 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" integrity sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA== +log-driver@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8" + integrity sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg== + loglevel@^1.6.8: version "1.7.0" resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.7.0.tgz#728166855a740d59d38db01cf46f042caa041bb0" @@ -9552,7 +9573,7 @@ request-promise-native@^1.0.5: stealthy-require "^1.1.1" tough-cookie "^2.3.3" -request@^2.87.0, request@^2.88.0: +request@^2.87.0, request@^2.88.0, request@^2.88.2: version "2.88.2" resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3" integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw== From 222a064239afcc9fc7b1cf1ad3173ed4f5e86103 Mon Sep 17 00:00:00 2001 From: Alexandre Henrique Afonso Campos Date: Tue, 8 Sep 2020 22:55:35 -0300 Subject: [PATCH 10/21] Ask travis to upload coverage report --- .travis.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 3d5279314..9905eb699 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,4 +13,6 @@ jobs: script: - cd tnoodle-ui - npm install - - npm run test + - npm test -- --coverage + after_script: + - COVERALLS_REPO_TOKEN=$coveralls_repo_token npm run coveralls From 8f656e3e884279d281fd01c997e6c9e7742f6ec6 Mon Sep 17 00:00:00 2001 From: Alexandre Henrique Afonso Campos Date: Tue, 8 Sep 2020 22:59:24 -0300 Subject: [PATCH 11/21] Add badge to README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2721f753a..5f99aca23 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ TNoodle is a software suite that contains the official WCA scramble program. It consists of the core scrambling code (primarily written in Java) as well as a UI and server to generate a fully autonomous JAR file -[![Build Status](https://travis-ci.org/thewca/tnoodle.svg?branch=master)](https://travis-ci.org/thewca/tnoodle) +[![Build Status](https://travis-ci.org/thewca/tnoodle.svg?branch=master)](https://travis-ci.org/thewca/tnoodle) [![Coverage Status](https://coveralls.io/repos/github/thewca/tnoodle/badge.svg?branch=master)](https://coveralls.io/github/thewca/tnoodle?branch=master) ## WCA Scramble Program From b9bddc849d35474921323726e5ead5c9a1be2bcd Mon Sep 17 00:00:00 2001 From: Alexandre Henrique Afonso Campos Date: Wed, 9 Sep 2020 07:46:27 -0300 Subject: [PATCH 12/21] Remove GitHub pages script --- tnoodle-ui/package.json | 2 -- 1 file changed, 2 deletions(-) diff --git a/tnoodle-ui/package.json b/tnoodle-ui/package.json index 62b38142e..4616ec169 100644 --- a/tnoodle-ui/package.json +++ b/tnoodle-ui/package.json @@ -28,8 +28,6 @@ "build": "react-scripts build", "test": "react-scripts test --watchAll --watchAll=false", "eject": "react-scripts eject", - "predeploy": "npm run build", - "deploy": "gh-pages -d build", "coveralls": "cat ./coverage/lcov.info | node node_modules/.bin/coveralls" }, "browserslist": { From 9143346c5ac10761ce7de6326708f867e91b7096 Mon Sep 17 00:00:00 2001 From: Alexandre Henrique Afonso Campos Date: Thu, 10 Sep 2020 02:00:18 -0300 Subject: [PATCH 13/21] WIP Add test for Main --- .../src/components/EventPickerTable.jsx | 8 +- .../src/components/FmcTranslationsDetail.jsx | 15 +- tnoodle-ui/src/components/Main.test.js | 283 ++++++++++++++++++ 3 files changed, 297 insertions(+), 9 deletions(-) create mode 100644 tnoodle-ui/src/components/Main.test.js diff --git a/tnoodle-ui/src/components/EventPickerTable.jsx b/tnoodle-ui/src/components/EventPickerTable.jsx index bdd81a40f..dda5fe089 100644 --- a/tnoodle-ui/src/components/EventPickerTable.jsx +++ b/tnoodle-ui/src/components/EventPickerTable.jsx @@ -49,8 +49,8 @@ const EventPickerTable = connect( return response.json(); } }) - .then((formats) => { - this.props.setWcaFormats(formats); + .then((response) => { + this.props.setWcaFormats(response); }); }; @@ -61,8 +61,8 @@ const EventPickerTable = connect( return response.json(); } }) - .then((wcaEvents) => { - this.props.setWcaEvents(wcaEvents); + .then((response) => { + this.props.setWcaEvents(response); }); }; diff --git a/tnoodle-ui/src/components/FmcTranslationsDetail.jsx b/tnoodle-ui/src/components/FmcTranslationsDetail.jsx index 8946b857d..fc43b5e2a 100644 --- a/tnoodle-ui/src/components/FmcTranslationsDetail.jsx +++ b/tnoodle-ui/src/components/FmcTranslationsDetail.jsx @@ -141,18 +141,23 @@ const FmcTranslationsDetail = connect( checked={ translation.status } - onChange={(e) => + onChange={( + e + ) => this.handleTranslation( - translation.id, e.target.checked + translation.id, + e + .target + .checked ) } /> {j < TRANSLATIONS_PER_LINE - - 1 && ( - - )} + 1 && ( + + )} ); } diff --git a/tnoodle-ui/src/components/Main.test.js b/tnoodle-ui/src/components/Main.test.js new file mode 100644 index 000000000..51ccac6ed --- /dev/null +++ b/tnoodle-ui/src/components/Main.test.js @@ -0,0 +1,283 @@ +import React from "react"; +import { act } from "react-dom/test-utils"; + +import { render, unmountComponentAtNode } from "react-dom"; +import { fireEvent } from "@testing-library/react"; + +import { Provider } from "react-redux"; +import store from "../redux/Store"; + +import Main from "./Main"; + +const tnoodleApi = require("../api/tnoodle.api"); + +let container = null; +beforeEach(() => { + // setup a DOM element as a render target + container = document.createElement("div"); + document.body.appendChild(container); +}); + +afterEach(() => { + // cleanup on exiting + unmountComponentAtNode(container); + container.remove(); + container = null; +}); + +it("There should be only 1 button of type submit", async () => { + // Define mock objects + const events = [ + { + id: "333", + name: "3x3x3", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "222", + name: "2x2x2", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "444", + name: "4x4x4", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "555", + name: "5x5x5", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "666", + name: "6x6x6", + format_ids: ["m", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "777", + name: "7x7x7", + format_ids: ["m", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "333bf", + name: "3x3x3 Blindfolded", + format_ids: ["3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "333fm", + name: "3x3x3 Fewest Moves", + format_ids: ["m", "2", "1"], + can_change_time_limit: false, + is_timed_event: false, + is_fewest_moves: true, + is_multiple_blindfolded: false, + }, + { + id: "333oh", + name: "3x3x3 One-Handed", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "clock", + name: "Clock", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "minx", + name: "Megaminx", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "pyram", + name: "Pyraminx", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "skewb", + name: "Skewb", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "sq1", + name: "Square-1", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "444bf", + name: "4x4x4 Blindfolded", + format_ids: ["3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "555bf", + name: "5x5x5 Blindfolded", + format_ids: ["3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "333mbf", + name: "3x3x3 Multiple Blindfolded", + format_ids: ["3", "2", "1"], + can_change_time_limit: false, + is_timed_event: false, + is_fewest_moves: false, + is_multiple_blindfolded: true, + }, + ]; + + const formats = { + 1: { name: "Best of 1", shortName: "Bo1" }, + 2: { name: "Best of 2", shortName: "Bo2" }, + 3: { name: "Best of 3", shortName: "Bo3" }, + a: { name: "Average of 5", shortName: "Ao5" }, + m: { name: "Mean of 3", shortName: "Mo3" }, + }; + + const languages = { + da: "Danish", + de: "German", + en: "English", + es: "Spanish", + et: "Estonian", + fi: "Finnish", + fr: "French", + hr: "Croatian", + hu: "Hungarian", + id: "Indonesian", + it: "Italian", + ja: "Japanese", + ko: "Korean", + pl: "Polish", + pt: "Portuguese", + "pt-BR": "Portuguese (Brazil)", + ro: "Romanian", + ru: "Russian", + sl: "Slovenian", + vi: "Vietnamese", + "zh-CN": "Chinese (China)", + "zh-TW": "Chinese (Taiwan)", + }; + + // Turn on mocking behavior + jest.spyOn(tnoodleApi, "fetchWcaEvents").mockImplementation(() => + Promise.resolve(new Response(JSON.stringify(events))) + ); + + jest.spyOn(tnoodleApi, "fetchFormats").mockImplementation(() => + Promise.resolve(new Response(JSON.stringify(formats))) + ); + + jest.spyOn( + tnoodleApi, + "fetchAvailableFmcTranslations" + ).mockImplementation(() => + Promise.resolve(new Response(JSON.stringify(languages))) + ); + + // Render component + await act(async () => { + render( + +
+ , + container + ); + }); + + // Pick all . This will include 17 rounds selector + // and 1 format selector (for 3x3x3, which already has 1 round) + const selects = container.querySelectorAll("select"); + + // Change all rounds from 0 to 1 (side effect, change 3x3x3 from Ao5 to another value) + // the objective here is to open all events + selects.forEach((select) => { + fireEvent.change(select, { + target: { value: "1" }, + }); + }); + + // Take the form and all the buttons inside of it + const form = container.querySelector("form"); + const buttons = Array.from(form.querySelectorAll("button")); + + // Click almost all buttons. The point here is to open translations, + // but it won't hurt click other buttons as well. + // By avoiding Generate Scrambles button, we avoid triggering zip generation, + // therefore button text, which is used later. + buttons + .filter((button) => button.innerHTML !== "Generate Scrambles") + .forEach((button) => { + fireEvent.click(button); + }); + + const completeButtons = Array.from(form.querySelectorAll("button")); + completeButtons.forEach((b) => console.log(b.innerHTML)); + + const buttonsTypeSubmit = completeButtons.filter( + (button) => button.type === "submit" + ); + + // There can be only 1 button of type submit inside the form + expect(buttonsTypeSubmit.length).toBe(1); + + // The only submit button must be Generate Scrambles + const button = buttonsTypeSubmit[0]; + expect(button.innerHTML).toBe("Generate Scrambles"); }); diff --git a/tnoodle-ui/src/test/Main.test.js b/tnoodle-ui/src/test/Main.test.js deleted file mode 100644 index a4effc2ec..000000000 --- a/tnoodle-ui/src/test/Main.test.js +++ /dev/null @@ -1,300 +0,0 @@ -import React from "react"; -import { act } from "react-dom/test-utils"; - -import { render, unmountComponentAtNode } from "react-dom"; -import { fireEvent } from "@testing-library/react"; - -import { Provider } from "react-redux"; -import store from "../main/redux/Store"; - -import Main from "../main/components/Main"; - -const tnoodleApi = require("../main/api/tnoodle.api"); -const wcaApi = require("../main/api/wca.api"); - -let container = null; -beforeEach(() => { - // setup a DOM element as a render target - container = document.createElement("div"); - document.body.appendChild(container); -}); - -afterEach(() => { - // cleanup on exiting - unmountComponentAtNode(container); - container.remove(); - container = null; -}); - -it("There should be only 1 button of type submit", async () => { - // Define mock objects - const events = [ - { - id: "333", - name: "3x3x3", - format_ids: ["a", "3", "2", "1"], - can_change_time_limit: true, - is_timed_event: true, - is_fewest_moves: false, - is_multiple_blindfolded: false, - }, - { - id: "222", - name: "2x2x2", - format_ids: ["a", "3", "2", "1"], - can_change_time_limit: true, - is_timed_event: true, - is_fewest_moves: false, - is_multiple_blindfolded: false, - }, - { - id: "444", - name: "4x4x4", - format_ids: ["a", "3", "2", "1"], - can_change_time_limit: true, - is_timed_event: true, - is_fewest_moves: false, - is_multiple_blindfolded: false, - }, - { - id: "555", - name: "5x5x5", - format_ids: ["a", "3", "2", "1"], - can_change_time_limit: true, - is_timed_event: true, - is_fewest_moves: false, - is_multiple_blindfolded: false, - }, - { - id: "666", - name: "6x6x6", - format_ids: ["m", "2", "1"], - can_change_time_limit: true, - is_timed_event: true, - is_fewest_moves: false, - is_multiple_blindfolded: false, - }, - { - id: "777", - name: "7x7x7", - format_ids: ["m", "2", "1"], - can_change_time_limit: true, - is_timed_event: true, - is_fewest_moves: false, - is_multiple_blindfolded: false, - }, - { - id: "333bf", - name: "3x3x3 Blindfolded", - format_ids: ["3", "2", "1"], - can_change_time_limit: true, - is_timed_event: true, - is_fewest_moves: false, - is_multiple_blindfolded: false, - }, - { - id: "333fm", - name: "3x3x3 Fewest Moves", - format_ids: ["m", "2", "1"], - can_change_time_limit: false, - is_timed_event: false, - is_fewest_moves: true, - is_multiple_blindfolded: false, - }, - { - id: "333oh", - name: "3x3x3 One-Handed", - format_ids: ["a", "3", "2", "1"], - can_change_time_limit: true, - is_timed_event: true, - is_fewest_moves: false, - is_multiple_blindfolded: false, - }, - { - id: "clock", - name: "Clock", - format_ids: ["a", "3", "2", "1"], - can_change_time_limit: true, - is_timed_event: true, - is_fewest_moves: false, - is_multiple_blindfolded: false, - }, - { - id: "minx", - name: "Megaminx", - format_ids: ["a", "3", "2", "1"], - can_change_time_limit: true, - is_timed_event: true, - is_fewest_moves: false, - is_multiple_blindfolded: false, - }, - { - id: "pyram", - name: "Pyraminx", - format_ids: ["a", "3", "2", "1"], - can_change_time_limit: true, - is_timed_event: true, - is_fewest_moves: false, - is_multiple_blindfolded: false, - }, - { - id: "skewb", - name: "Skewb", - format_ids: ["a", "3", "2", "1"], - can_change_time_limit: true, - is_timed_event: true, - is_fewest_moves: false, - is_multiple_blindfolded: false, - }, - { - id: "sq1", - name: "Square-1", - format_ids: ["a", "3", "2", "1"], - can_change_time_limit: true, - is_timed_event: true, - is_fewest_moves: false, - is_multiple_blindfolded: false, - }, - { - id: "444bf", - name: "4x4x4 Blindfolded", - format_ids: ["3", "2", "1"], - can_change_time_limit: true, - is_timed_event: true, - is_fewest_moves: false, - is_multiple_blindfolded: false, - }, - { - id: "555bf", - name: "5x5x5 Blindfolded", - format_ids: ["3", "2", "1"], - can_change_time_limit: true, - is_timed_event: true, - is_fewest_moves: false, - is_multiple_blindfolded: false, - }, - { - id: "333mbf", - name: "3x3x3 Multiple Blindfolded", - format_ids: ["3", "2", "1"], - can_change_time_limit: false, - is_timed_event: false, - is_fewest_moves: false, - is_multiple_blindfolded: true, - }, - ]; - - const formats = { - 1: { name: "Best of 1", shortName: "Bo1" }, - 2: { name: "Best of 2", shortName: "Bo2" }, - 3: { name: "Best of 3", shortName: "Bo3" }, - a: { name: "Average of 5", shortName: "Ao5" }, - m: { name: "Mean of 3", shortName: "Mo3" }, - }; - - const languages = { - da: "Danish", - de: "German", - en: "English", - es: "Spanish", - et: "Estonian", - fi: "Finnish", - fr: "French", - hr: "Croatian", - hu: "Hungarian", - id: "Indonesian", - it: "Italian", - ja: "Japanese", - ko: "Korean", - pl: "Polish", - pt: "Portuguese", - "pt-BR": "Portuguese (Brazil)", - ro: "Romanian", - ru: "Russian", - sl: "Slovenian", - vi: "Vietnamese", - "zh-CN": "Chinese (China)", - "zh-TW": "Chinese (Taiwan)", - }; - - const me = { - me: { - wca_id: "2010AAAA01", - name: "User Name", - }, - }; - - // Turn on mocking behavior - jest.spyOn(tnoodleApi, "fetchWcaEvents").mockImplementation(() => - Promise.resolve(new Response(JSON.stringify(events))) - ); - - jest.spyOn(tnoodleApi, "fetchFormats").mockImplementation(() => - Promise.resolve(new Response(JSON.stringify(formats))) - ); - - jest.spyOn( - tnoodleApi, - "fetchAvailableFmcTranslations" - ).mockImplementation(() => - Promise.resolve(new Response(JSON.stringify(languages))) - ); - - jest.spyOn(wcaApi, "fetchMe").mockImplementation(() => - Promise.resolve(new Response(JSON.stringify(me))) - ); - - // Render component - await act(async () => { - render( - -
- , - container - ); - }); - - // Pick all . This will include 17 rounds selector - // and 1 format selector (for 3x3x3, which already has 1 round) - const selects = container.querySelectorAll("select"); - - // Change all rounds from 0 to 1 (side effect, change 3x3x3 from Ao5 to another value) - // the objective here is to open all events - selects.forEach((select) => { - fireEvent.change(select, { - target: { value: "1" }, - }); - }); - - // Take the form and all the buttons inside of it - const form = container.querySelector("form"); - const buttons = Array.from(form.querySelectorAll("button")); - - // Click almost all buttons. The point here is to open translations, - // but it won't hurt click other buttons as well. - // By avoiding Generate Scrambles button, we avoid triggering zip generation, - // therefore button text, which is used later. - buttons - .filter((button) => button.innerHTML !== "Generate Scrambles") - .forEach((button) => { - fireEvent.click(button); - }); - - const completeButtons = Array.from(form.querySelectorAll("button")); - completeButtons.forEach((b) => console.log(b.innerHTML)); - - const buttonsTypeSubmit = completeButtons.filter( - (button) => button.type === "submit" - ); - - // There can be only 1 button of type submit inside the form - expect(buttonsTypeSubmit.length).toBe(1); - - // The only submit button must be Generate Scrambles - const button = buttonsTypeSubmit[0]; - expect(button.innerHTML).toBe("Generate Scrambles"); + const linkElement = getByText(/Log in/i); + expect(linkElement).toBeInTheDocument(); }); diff --git a/tnoodle-ui/src/test/Main.test.js b/tnoodle-ui/src/test/Main.test.js new file mode 100644 index 000000000..fbf3686b1 --- /dev/null +++ b/tnoodle-ui/src/test/Main.test.js @@ -0,0 +1,291 @@ +import React from "react"; +import { act } from "react-dom/test-utils"; + +import { render, unmountComponentAtNode } from "react-dom"; +import { fireEvent } from "@testing-library/react"; + +import { Provider } from "react-redux"; +import store from "../main/redux/Store"; +import { + updateTranslations, + addSuggestedFmcTranslations, +} from "../main/redux/ActionCreators"; + +import Main from "../main/components/Main"; + +const tnoodleApi = require("../main/api/tnoodle.api"); +const wcaApi = require("../main/api/wca.api"); + +let container = null; +beforeEach(() => { + // setup a DOM element as a render target + container = document.createElement("div"); + document.body.appendChild(container); +}); + +afterEach(() => { + // cleanup on exiting + unmountComponentAtNode(container); + container.remove(); + container = null; +}); + +it("There should be only 1 button of type submit", async () => { + // Define mock objects + const events = [ + { + id: "333", + name: "3x3x3", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "222", + name: "2x2x2", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "444", + name: "4x4x4", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "555", + name: "5x5x5", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "666", + name: "6x6x6", + format_ids: ["m", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "777", + name: "7x7x7", + format_ids: ["m", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "333bf", + name: "3x3x3 Blindfolded", + format_ids: ["3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "333fm", + name: "3x3x3 Fewest Moves", + format_ids: ["m", "2", "1"], + can_change_time_limit: false, + is_timed_event: false, + is_fewest_moves: true, + is_multiple_blindfolded: false, + }, + { + id: "333oh", + name: "3x3x3 One-Handed", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "clock", + name: "Clock", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "minx", + name: "Megaminx", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "pyram", + name: "Pyraminx", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "skewb", + name: "Skewb", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "sq1", + name: "Square-1", + format_ids: ["a", "3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "444bf", + name: "4x4x4 Blindfolded", + format_ids: ["3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "555bf", + name: "5x5x5 Blindfolded", + format_ids: ["3", "2", "1"], + can_change_time_limit: true, + is_timed_event: true, + is_fewest_moves: false, + is_multiple_blindfolded: false, + }, + { + id: "333mbf", + name: "3x3x3 Multiple Blindfolded", + format_ids: ["3", "2", "1"], + can_change_time_limit: false, + is_timed_event: false, + is_fewest_moves: false, + is_multiple_blindfolded: true, + }, + ]; + + const formats = { + 1: { name: "Best of 1", shortName: "Bo1" }, + 2: { name: "Best of 2", shortName: "Bo2" }, + 3: { name: "Best of 3", shortName: "Bo3" }, + a: { name: "Average of 5", shortName: "Ao5" }, + m: { name: "Mean of 3", shortName: "Mo3" }, + }; + + const languages = { + da: "Danish", + de: "German", + en: "English", + es: "Spanish", + et: "Estonian", + fi: "Finnish", + fr: "French", + hr: "Croatian", + hu: "Hungarian", + id: "Indonesian", + it: "Italian", + ja: "Japanese", + ko: "Korean", + pl: "Polish", + pt: "Portuguese", + "pt-BR": "Portuguese (Brazil)", + ro: "Romanian", + ru: "Russian", + sl: "Slovenian", + vi: "Vietnamese", + "zh-CN": "Chinese (China)", + "zh-TW": "Chinese (Taiwan)", + }; + + // Turn on mocking behavior + jest.spyOn(tnoodleApi, "fetchWcaEvents").mockImplementation(() => + Promise.resolve(new Response(JSON.stringify(events))) + ); + + jest.spyOn(tnoodleApi, "fetchFormats").mockImplementation(() => + Promise.resolve(new Response(JSON.stringify(formats))) + ); + + // We add suggested FMC so the button Select Suggested appears as well + const translations = Object.keys(languages).map((translationId) => ({ + id: translationId, + display: languages[translationId], + status: true, + })); + const suggestedFmcTranslations = ["de", "en", "pt-BR"]; + + store.dispatch(updateTranslations(translations)); + store.dispatch(addSuggestedFmcTranslations(suggestedFmcTranslations)); + + // Render component + await act(async () => { + render( + +
+ , + container + ); + }); + + // Pick all