diff --git a/.github/workflows/end_to_end_tests.yml b/.github/workflows/end_to_end_tests.yml index a7b3ecfb..d6370f10 100644 --- a/.github/workflows/end_to_end_tests.yml +++ b/.github/workflows/end_to_end_tests.yml @@ -20,6 +20,15 @@ jobs: - name: "Checkout" uses: "actions/checkout@v2.0.0" + - name: "Setup NodeJS" + uses: actions/setup-node@v1 + with: + node-version: '14.x' + + - name: "Install dependencies" + run: npm install + working-directory: "tests" + - name: "Setup .env file" run: cp .env.template .env @@ -27,10 +36,10 @@ jobs: run: sudo chown 1000:1000 -R . - name: "Start environment" - run: docker-compose up -d + run: LIVE_RELOAD=0 docker-compose up -d - name: "Wait for environment to build (and downloading testcafe image)" - run: (docker-compose -f docker-compose.testcafe.yml pull &) && docker-compose logs -f --tail=0 front | grep -q "Compiled successfully" + run: (docker-compose -f docker-compose.testcafe.yml build &) && docker-compose logs -f --tail=0 front | grep -q "Compiled successfully" # - name: "temp debug: display logs" # run: docker-compose logs @@ -42,7 +51,7 @@ jobs: # run: docker-compose logs -f pusher | grep -q "WorkAdventure starting on port" - name: "Run tests" - run: docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe + run: PROJECT_DIR=$(pwd) docker-compose -f docker-compose.testcafe.yml up --exit-code-from testcafe - name: Upload failed tests if: ${{ failure() }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8bbbc93e..e5d406cf 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,9 +59,43 @@ $ docker-compose exec back yarn run pretty WorkAdventure is based on a video game engine (Phaser), and video games are not the easiest programs to unit test. -Nevertheless, if your code can be unit tested, please provide a unit test (we use Jasmine). +Nevertheless, if your code can be unit tested, please provide a unit test (we use Jasmine), or an end-to-end test (we use Testcafe). If you are providing a new feature, you should setup a test map in the `maps/tests` directory. The test map should contain -some description text describing how to test the feature. Finally, you should modify the `maps/tests/index.html` file -to add a reference to your newly created test map. +some description text describing how to test the feature. +* if the features is meant to be manually tested, you should modify the `maps/tests/index.html` file to add a reference + to your newly created test map +* if the features can be automatically tested, please provide a testcafe test + +#### Running testcafe tests + +End-to-end tests are available in the "/tests" directory. + +To run these tests locally: + +```console +$ LIVE_RELOAD=0 docker-compose up -d +$ cd tests +$ npm install +$ npm run test +``` + +Note: If your tests fail on a Javascript error in "sockjs", this is due to the +Webpack live reload. The Webpack live reload feature is conflicting with testcafe. This is why we recommend starting +WorkAdventure with the `LIVE_RELOAD=0` environment variable. + +End-to-end tests can take a while to run. To run only one test, use: + +```console +$ npm run test -- tests/[name of the test file].ts +``` + +You can also run the tests inside a container (but you will not have visual feedbacks on your test, so we recommend using +the local tests). + +```console +$ LIVE_RELOAD=0 docker-compose up -d +# Wait 2-3 minutes for the environment to start, then: +$ PROJECT_DIR=$(pwd) docker-compose -f docker-compose.testcafe.yml up +``` diff --git a/back/src/Controller/DebugController.ts b/back/src/Controller/DebugController.ts index 88287753..8fbf82e4 100644 --- a/back/src/Controller/DebugController.ts +++ b/back/src/Controller/DebugController.ts @@ -12,43 +12,52 @@ export class DebugController { getDump() { this.App.get("/dump", (res: HttpResponse, req: HttpRequest) => { - const query = parse(req.getQuery()); + (async () => { + const query = parse(req.getQuery()); - if (query.token !== ADMIN_API_TOKEN) { - return res.writeStatus("401 Unauthorized").end("Invalid token sent!"); - } + if (query.token !== ADMIN_API_TOKEN) { + return res.writeStatus("401 Unauthorized").end("Invalid token sent!"); + } - return res - .writeStatus("200 OK") - .writeHeader("Content-Type", "application/json") - .end( - stringify(socketManager.getWorlds(), (key: unknown, value: unknown) => { - if (key === "listeners") { - return "Listeners"; - } - if (key === "socket") { - return "Socket"; - } - if (key === "batchedMessages") { - return "BatchedMessages"; - } - if (value instanceof Map) { - const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any - for (const [mapKey, mapValue] of value.entries()) { - obj[mapKey] = mapValue; + return res + .writeStatus("200 OK") + .writeHeader("Content-Type", "application/json") + .end( + stringify( + await Promise.all(socketManager.getWorlds().values()), + (key: unknown, value: unknown) => { + if (key === "listeners") { + return "Listeners"; + } + if (key === "socket") { + return "Socket"; + } + if (key === "batchedMessages") { + return "BatchedMessages"; + } + if (value instanceof Map) { + const obj: any = {}; // eslint-disable-line @typescript-eslint/no-explicit-any + for (const [mapKey, mapValue] of value.entries()) { + obj[mapKey] = mapValue; + } + return obj; + } else if (value instanceof Set) { + const obj: Array = []; + for (const [setKey, setValue] of value.entries()) { + obj.push(setValue); + } + return obj; + } else { + return value; + } } - return obj; - } else if (value instanceof Set) { - const obj: Array = []; - for (const [setKey, setValue] of value.entries()) { - obj.push(setValue); - } - return obj; - } else { - return value; - } - }) - ); + ) + ); + })().catch((e) => { + console.error(e); + res.writeStatus("500"); + res.end("An error occurred"); + }); }); } } diff --git a/docker-compose.testcafe.yml b/docker-compose.testcafe.yml index 774477a1..e61db21d 100644 --- a/docker-compose.testcafe.yml +++ b/docker-compose.testcafe.yml @@ -1,12 +1,19 @@ -version: "3" +version: "3.5" services: testcafe: - image: testcafe/testcafe:1.17.1 - working_dir: /tests + build: tests/ + working_dir: /project/tests + command: + - --dev + # Run as root to have the right to access /var/run/docker.sock + user: root environment: BROWSER: "chromium --use-fake-device-for-media-stream" + PROJECT_DIR: ${PROJECT_DIR} + ADMIN_API_TOKEN: ${ADMIN_API_TOKEN} volumes: - - ./tests:/tests + - ./:/project - ./maps:/maps + - /var/run/docker.sock:/var/run/docker.sock # security_opt: # - seccomp:unconfined diff --git a/docker-compose.yaml b/docker-compose.yaml index e5e6489c..17e04f7c 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,20 +1,21 @@ -version: "3" +version: "3.5" services: reverse-proxy: - image: traefik:v2.0 + image: traefik:v2.5 command: - --api.insecure=true - --providers.docker - --entryPoints.web.address=:80 - --entryPoints.websecure.address=:443 + - "--providers.docker.exposedbydefault=false" ports: - "80:80" - "443:443" # The Web UI (enabled by --api.insecure=true) - "8080:8080" - depends_on: - - back - - front + #depends_on: + # - back + # - front volumes: - /var/run/docker.sock:/var/run/docker.sock networks: @@ -51,10 +52,12 @@ services: MAX_USERNAME_LENGTH: "$MAX_USERNAME_LENGTH" DISABLE_ANONYMOUS: "$DISABLE_ANONYMOUS" OPID_LOGIN_SCREEN_PROVIDER: "$OPID_LOGIN_SCREEN_PROVIDER" + LIVE_RELOAD: "$LIVE_RELOAD:-true" command: yarn run start volumes: - ./front:/usr/src/app labels: + - "traefik.enable=true" - "traefik.http.routers.front.rule=Host(`play.workadventure.localhost`)" - "traefik.http.routers.front.entryPoints=web" - "traefik.http.services.front.loadbalancer.server.port=8080" @@ -87,6 +90,7 @@ services: volumes: - ./pusher:/usr/src/app labels: + - "traefik.enable=true" - "traefik.http.routers.pusher.rule=Host(`pusher.workadventure.localhost`)" - "traefik.http.routers.pusher.entryPoints=web" - "traefik.http.services.pusher.loadbalancer.server.port=8080" @@ -111,6 +115,7 @@ services: volumes: - ./maps:/var/www/html labels: + - "traefik.enable=true" - "traefik.http.routers.maps.rule=Host(`maps.workadventure.localhost`)" - "traefik.http.routers.maps.entryPoints=web,traefik" - "traefik.http.services.maps.loadbalancer.server.port=80" @@ -142,6 +147,7 @@ services: volumes: - ./back:/usr/src/app labels: + - "traefik.enable=true" - "traefik.http.routers.back.rule=Host(`api.workadventure.localhost`)" - "traefik.http.routers.back.entryPoints=web" - "traefik.http.services.back.loadbalancer.server.port=8080" @@ -160,6 +166,7 @@ services: volumes: - ./uploader:/usr/src/app labels: + - "traefik.enable=true" - "traefik.http.routers.uploader.rule=Host(`uploader.workadventure.localhost`)" - "traefik.http.routers.uploader.entryPoints=web" - "traefik.http.services.uploader.loadbalancer.server.port=8080" @@ -187,6 +194,7 @@ services: redisinsight: image: redislabs/redisinsight:latest labels: + - "traefik.enable=true" - "traefik.http.routers.redisinsight.rule=Host(`redis.workadventure.localhost`)" - "traefik.http.routers.redisinsight.entryPoints=web" - "traefik.http.services.redisinsight.loadbalancer.server.port=8001" @@ -198,6 +206,7 @@ services: icon: image: matthiasluedtke/iconserver:v3.13.0 labels: + - "traefik.enable=true" - "traefik.http.routers.icon.rule=Host(`icon.workadventure.localhost`)" - "traefik.http.routers.icon.entryPoints=web" - "traefik.http.services.icon.loadbalancer.server.port=8080" diff --git a/front/.eslintrc.json b/front/.eslintrc.js similarity index 75% rename from front/.eslintrc.json rename to front/.eslintrc.js index 45b44456..33466012 100644 --- a/front/.eslintrc.json +++ b/front/.eslintrc.js @@ -1,4 +1,4 @@ -{ +module.exports = { "root": true, "env": { "browser": true, @@ -18,10 +18,18 @@ "parserOptions": { "ecmaVersion": 2018, "sourceType": "module", - "project": "./tsconfig.json" + "project": "./tsconfig.json", + "extraFileExtensions": [".svelte"] }, "plugins": [ - "@typescript-eslint" + "@typescript-eslint", + "svelte3" + ], + "overrides": [ + { + "files": ["*.svelte"], + "processor": "svelte3/svelte3" + } ], "rules": { "no-unused-vars": "off", @@ -34,5 +42,9 @@ "@typescript-eslint/no-unsafe-return": "off", "@typescript-eslint/no-unsafe-member-access": "off", "@typescript-eslint/restrict-template-expressions": "off" + }, + "settings": { + "svelte3/typescript": true, + "svelte3/ignore-styles": () => true } } diff --git a/front/.prettierrc.json b/front/.prettierrc.json index e8980d15..057ed062 100644 --- a/front/.prettierrc.json +++ b/front/.prettierrc.json @@ -1,4 +1,5 @@ { "printWidth": 120, - "tabWidth": 4 + "tabWidth": 4, + "plugins": ["prettier-plugin-svelte"] } diff --git a/front/package.json b/front/package.json index ceda9abc..8c1ef6c7 100644 --- a/front/package.json +++ b/front/package.json @@ -16,7 +16,7 @@ "@typescript-eslint/parser": "^4.23.0", "css-loader": "^5.2.4", "eslint": "^7.26.0", - "file-loader": "^6.2.0", + "eslint-plugin-svelte3": "^3.2.1", "fork-ts-checker-webpack-plugin": "^6.2.9", "html-webpack-plugin": "^5.3.1", "jasmine": "^3.5.0", @@ -25,6 +25,7 @@ "node-polyfill-webpack-plugin": "^1.1.2", "npm-run-all": "^4.1.5", "prettier": "^2.3.1", + "prettier-plugin-svelte": "^2.5.0", "sass": "^1.32.12", "sass-loader": "^11.1.0", "svelte": "^3.38.2", @@ -56,6 +57,7 @@ "queue-typescript": "^1.0.1", "quill": "1.3.6", "quill-delta-to-html": "^0.12.0", + "retry-axios": "^2.6.0", "rxjs": "^6.6.3", "sanitize-html": "^2.5.0", "simple-peer": "^9.11.0", @@ -70,17 +72,21 @@ "build": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production webpack", "build-typings": "cross-env TS_NODE_PROJECT=\"tsconfig-for-webpack.json\" NODE_ENV=production BUILD_TYPINGS=1 webpack", "test": "cross-env TS_NODE_PROJECT=\"tsconfig-for-jasmine.json\" ts-node node_modules/jasmine/bin/jasmine --config=jasmine.json", - "lint": "node_modules/.bin/eslint src/ . --ext .ts", - "fix": "node_modules/.bin/eslint --fix src/ . --ext .ts", + "lint": "node_modules/.bin/eslint src/ tests/ --ext .ts,.svelte", + "fix": "node_modules/.bin/eslint --fix src/ tests/ --ext .ts,.svelte", "precommit": "lint-staged", "svelte-check-watch": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\" --watch", "svelte-check": "svelte-check --fail-on-warnings --fail-on-hints --compiler-warnings \"a11y-no-onchange:ignore,a11y-autofocus:ignore,a11y-media-has-caption:ignore\"", - "pretty": "yarn prettier --write 'src/**/*.{ts,tsx}'", - "pretty-check": "yarn prettier --check 'src/**/*.{ts,tsx}'" + "pretty": "yarn prettier --write 'src/**/*.{ts,svelte}'", + "pretty-check": "yarn prettier --check 'src/**/*.{ts,svelte}'" }, "lint-staged": { - "*.ts": [ - "prettier --write" + "*.svelte": [ + "yarn run svelte-check" + ], + "*.{ts,svelte}": [ + "yarn run fix", + "yarn run pretty" ] } } diff --git a/front/src/Components/App.svelte b/front/src/Components/App.svelte index ab8b6d3f..4886cc4e 100644 --- a/front/src/Components/App.svelte +++ b/front/src/Components/App.svelte @@ -1,147 +1,146 @@
{#if $loginSceneVisibleStore}
- +
{/if} {#if $selectCharacterSceneVisibleStore}
- +
{/if} {#if $customCharacterSceneVisibleStore}
- +
{/if} {#if $selectCompanionSceneVisibleStore}
- +
{/if} {#if $enableCameraSceneVisibilityStore}
- +
{/if} {#if $banMessageVisibleStore}
- +
{/if} {#if $textMessageVisibleStore}
- +
{/if} {#if $soundPlayingStore} -
- -
+
+ +
{/if} {#if $audioManagerVisibilityStore} -
- -
+
+ +
{/if} {#if $layoutManagerVisibilityStore}
- +
{/if} {#if $showReportScreenStore !== userReportEmpty}
- +
{/if} {#if $menuIconVisiblilityStore}
- +
{/if} {#if $menuVisiblilityStore}
- +
{/if} {#if $emoteMenuStore}
- +
{/if} {#if $gameOverlayVisibilityStore}
- - - + + +
{/if} {#if $helpCameraSettingsVisibleStore}
- +
{/if} {#if $requestVisitCardsStore} - + {/if} {#if $errorStore.length > 0} -
- -
+
+ +
{/if} {#if $chatVisibilityStore} - + {/if} {#if $warningContainerStore} - + {/if}
diff --git a/front/src/Components/AudioManager/AudioManager.svelte b/front/src/Components/AudioManager/AudioManager.svelte index 09798bc8..4422d002 100644 --- a/front/src/Components/AudioManager/AudioManager.svelte +++ b/front/src/Components/AudioManager/AudioManager.svelte @@ -1,10 +1,7 @@ -
- - - + + + + d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z" + /> + d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z" + /> + d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z" + /> - +
+
- \ No newline at end of file + diff --git a/front/src/Components/Chat/ChatElement.svelte b/front/src/Components/Chat/ChatElement.svelte index c5ae9e8c..3c006556 100644 --- a/front/src/Components/Chat/ChatElement.svelte +++ b/front/src/Components/Chat/ChatElement.svelte @@ -1,47 +1,55 @@
{#if message.type === ChatMessageTypes.userIncoming} - >> {#each targets as target, index}{#if !isLastIteration(index)}, {/if}{/each} entered ({renderDate(message.date)}) + >> {#each targets as target, index}{#if !isLastIteration(index)}, {/if}{/each} entered + ({renderDate(message.date)}) {:else if message.type === ChatMessageTypes.userOutcoming} - << {#each targets as target, index}{#if !isLastIteration(index)}, {/if}{/each} left ({renderDate(message.date)}) + << {#each targets as target, index}{#if !isLastIteration(index)}, {/if}{/each} left + ({renderDate(message.date)}) {:else if message.type === ChatMessageTypes.me}

Me: ({renderDate(message.date)})

{#each texts as text} -

{@html urlifyText(text)}

+

{@html urlifyText(text)}

{/each} {:else} -

: ({renderDate(message.date)})

+

: ({renderDate(message.date)})

{#each texts as text} -

{@html urlifyText(text)}

+

{@html urlifyText(text)}

{/each} {/if}
@@ -49,29 +57,33 @@ \ No newline at end of file + diff --git a/front/src/Components/Chat/ChatMessageForm.svelte b/front/src/Components/Chat/ChatMessageForm.svelte index cea4dd32..e12a4764 100644 --- a/front/src/Components/Chat/ChatMessageForm.svelte +++ b/front/src/Components/Chat/ChatMessageForm.svelte @@ -1,14 +1,14 @@
@@ -36,4 +36,4 @@ padding-left: 4px; padding-right: 4px; } - \ No newline at end of file + diff --git a/front/src/Components/Chat/ChatPlayerName.svelte b/front/src/Components/Chat/ChatPlayerName.svelte index 6611a253..f6cc2877 100644 --- a/front/src/Components/Chat/ChatPlayerName.svelte +++ b/front/src/Components/Chat/ChatPlayerName.svelte @@ -1,8 +1,8 @@ - {player.name} + {player.name} {#if isSubMenuOpen} - + {/if} - \ No newline at end of file + span.chatPlayerName { + margin-left: 3px; + } + .chatPlayerName:hover { + text-decoration: underline; + cursor: pointer; + } + diff --git a/front/src/Components/Chat/ChatSubMenu.svelte b/front/src/Components/Chat/ChatSubMenu.svelte index 6690699e..5a04a1b9 100644 --- a/front/src/Components/Chat/ChatSubMenu.svelte +++ b/front/src/Components/Chat/ChatSubMenu.svelte @@ -1,10 +1,9 @@
@@ -38,80 +37,100 @@

Customize your Avatar

- - + +
{#if $activeRowStore === 0} - + {/if} {#if $activeRowStore !== 0} - + {/if} {#if $activeRowStore === 5} - + {/if} {#if $activeRowStore !== 5} - + {/if}
diff --git a/front/src/Components/EmoteMenu/EmoteMenu.svelte b/front/src/Components/EmoteMenu/EmoteMenu.svelte index f8ccc5e8..d084718d 100644 --- a/front/src/Components/EmoteMenu/EmoteMenu.svelte +++ b/front/src/Components/EmoteMenu/EmoteMenu.svelte @@ -1,76 +1,72 @@ - +
-
+
diff --git a/front/src/Components/EnableCamera/EnableCameraScene.svelte b/front/src/Components/EnableCamera/EnableCameraScene.svelte index 1f9ef027..5993ee75 100644 --- a/front/src/Components/EnableCamera/EnableCameraScene.svelte +++ b/front/src/Components/EnableCamera/EnableCameraScene.svelte @@ -1,22 +1,22 @@

Turn on your camera and microphone

- {#if $localStreamStore.type === 'success' && $localStreamStore.stream} - - {:else } -
- -
- {/if} - + {#if $localStreamStore.type === "success" && $localStreamStore.stream} +