diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 143aab2ab4c..48620686c46 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -28,7 +28,7 @@ permissions:
jobs:
build:
if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
- name: ${{ matrix.target }} ${{ matrix.build_type }} # Update fetch-job-id step if changing this
+ name: ${{ matrix.target }} ${{ matrix.build_type }}${{ (matrix.target == 'core' && matrix.build_type == 'frontend' && format(' ({0})', matrix.browser)) || '' }} # Update fetch-job-id step if changing this
runs-on: ${{ (github.repository_owner == 'discourse' && 'debian-12') || 'ubuntu-latest' }}
container: discourse/discourse_test:release
timeout-minutes: 20
@@ -42,8 +42,9 @@ jobs:
MINIO_RUNNER_LOG_LEVEL: DEBUG
DISCOURSE_TURBO_RSPEC_RETRY_AND_LOG_FLAKY_TESTS: ${{ (matrix.build_type == 'system' || matrix.build_type == 'backend') && '1' }}
CHEAP_SOURCE_MAPS: "1"
- TESTEM_DEFAULT_BROWSER: Chrome
MINIO_RUNNER_INSTALL_DIR: /home/discourse/.minio_runner
+ TESTEM_BROWSER: ${{ (startsWith(matrix.browser, 'Firefox') && 'Firefox') || matrix.browser }}
+ TESTEM_FIREFOX_PATH: ${{ (matrix.browser == 'Firefox Evergreen') && '/opt/firefox-evergreen/firefox' }}
strategy:
fail-fast: false
@@ -51,6 +52,7 @@ jobs:
matrix:
build_type: [backend, frontend, system, annotations]
target: [core, plugins, themes]
+ browser: [Chrome]
exclude:
- build_type: annotations
target: plugins
@@ -58,11 +60,15 @@ jobs:
target: themes
- build_type: backend
target: themes
- - build_type: frontend
- target: core # Handled by core_frontend_tests job (below)
include:
- build_type: system
target: chat
+ - build_type: frontend
+ target: core
+ browser: Firefox Evergreen
+ - build_type: frontend
+ target: core
+ browser: Firefox ESR
steps:
- name: Set working directory owner
@@ -233,6 +239,11 @@ jobs:
if: matrix.build_type == 'backend' && matrix.target == 'plugins'
run: bin/rake plugin:turbo_spec['*','--verbose --format=documentation --use-runtime-info --profile=50']
+ - name: Core QUnit
+ if: matrix.build_type == 'frontend' && matrix.target == 'core'
+ run: QUNIT_WRITE_EXECUTION_FILE=1 bin/rake qunit:test
+ timeout-minutes: 30
+
- name: Plugin QUnit
if: matrix.build_type == 'frontend' && matrix.target == 'plugins'
run: QUNIT_WRITE_EXECUTION_FILE=1 bin/rake plugin:qunit['*']
@@ -244,9 +255,9 @@ jobs:
timeout-minutes: 10
- uses: actions/upload-artifact@v4
- if: always() && matrix.build_type == 'frontend' && matrix.target == 'plugins'
+ if: always() && matrix.build_type == 'frontend'
with:
- name: ember-exam-execution-plugins-frontend-${{ hashFiles('./app/assets/javascripts/discourse/test-execution-*.json') }}
+ name: ember-exam-execution-${{ matrix.target }}-${{ matrix.browser }}-frontend-${{ hashFiles('./app/assets/javascripts/discourse/test-execution-*.json') }}
path: ./app/assets/javascripts/discourse/test-execution-*.json
- name: Ember Build for System Tests
@@ -322,7 +333,7 @@ jobs:
id: fetch-job-id
if: steps.check-flaky-spec-report.outputs.exists == 'true'
run: |
- job_id=$(ruby script/get_github_workflow_run_job_id.rb ${{ github.run_id }} ${{ github.run_attempt }} '${{ matrix.target }} ${{ matrix.build_type }}')
+ job_id=$(ruby script/get_github_workflow_run_job_id.rb ${{ github.run_id }} ${{ github.run_attempt }} '${{ matrix.target }} ${{ matrix.build_type }}${{ (matrix.target == 'core' && matrix.build_type == 'frontend' && format(' ({0})', matrix.browser)) || '' }}')
echo "job_id=$job_id" >> $GITHUB_OUTPUT
- name: Create flaky tests report artifact
@@ -353,56 +364,6 @@ jobs:
fi
timeout-minutes: 30
- core_frontend_tests:
- if: github.event_name == 'pull_request' || github.repository != 'discourse/discourse-private-mirror'
- name: core frontend (${{ matrix.browser }})
- runs-on: ${{ (github.repository_owner == 'discourse' && 'debian-12') || 'ubuntu-latest' }}
- container:
- image: discourse/discourse_test:release
- options: --user discourse
-
- timeout-minutes: 35
-
- strategy:
- fail-fast: false
- matrix:
- browser: ["Chrome", "Firefox ESR", "Firefox Evergreen"]
-
- env:
- TESTEM_BROWSER: ${{ (startsWith(matrix.browser, 'Firefox') && 'Firefox') || matrix.browser }}
- TESTEM_FIREFOX_PATH: ${{ (matrix.browser == 'Firefox Evergreen') && '/opt/firefox-evergreen/firefox' }}
- CHEAP_SOURCE_MAPS: "1"
-
- steps:
- - uses: actions/checkout@v4
- with:
- fetch-depth: 1
-
- - name: Setup Git
- run: |
- git config --global user.email "ci@ci.invalid"
- git config --global user.name "Discourse CI"
-
- - name: pnpm install
- run: pnpm install --frozen-lockfile
-
- - name: Ember Build
- working-directory: ./app/assets/javascripts/discourse
- run: |
- mkdir /tmp/emberbuild
- pnpm ember build --environment=test -o /tmp/emberbuild
-
- - name: Core QUnit
- working-directory: ./app/assets/javascripts/discourse
- run: |
- pnpm ember exam --path /tmp/emberbuild --load-balance --parallel=$(($(nproc) / 2)) --launch "${{ env.TESTEM_BROWSER }}" --write-execution-file --random
- timeout-minutes: 15
-
- - uses: actions/upload-artifact@v4
- if: ${{ always() }}
- with:
- name: ember-exam-execution-${{ matrix.browser }}-${{ hashFiles('./app/assets/javascripts/discourse/test-execution-*.json') }}
- path: ./app/assets/javascripts/discourse/test-execution-*.json
merge:
if: github.repository == 'discourse/discourse' && github.ref == 'refs/heads/main'
diff --git a/app/assets/javascripts/discourse/ember-cli-build.js b/app/assets/javascripts/discourse/ember-cli-build.js
index dd1cfaf6368..44cac1ad74b 100644
--- a/app/assets/javascripts/discourse/ember-cli-build.js
+++ b/app/assets/javascripts/discourse/ember-cli-build.js
@@ -4,9 +4,7 @@ const EmberApp = require("ember-cli/lib/broccoli/ember-app");
const path = require("path");
const mergeTrees = require("broccoli-merge-trees");
const concat = require("broccoli-concat");
-const { createI18nTree } = require("./lib/translation-plugin");
const { parsePluginClientSettings } = require("./lib/site-settings-plugin");
-const discourseScss = require("./lib/discourse-scss");
const generateScriptsTree = require("./lib/scripts");
const funnel = require("broccoli-funnel");
const DeprecationSilencer = require("deprecation-silencer");
@@ -96,20 +94,12 @@ module.exports = function (defaults) {
const adminTree = app.project.findAddonByName("admin").treeForAddonBundle();
- const testStylesheetTree = mergeTrees([
- discourseScss(`${discourseRoot}/app/assets/stylesheets`, "qunit.scss"),
- discourseScss(
- `${discourseRoot}/app/assets/stylesheets`,
- "qunit-custom.scss"
- ),
- ]);
app.project.liveReloadFilterPatterns = [/.*\.scss/];
const terserPlugin = app.project.findAddonByName("ember-cli-terser");
const applyTerser = (tree) => terserPlugin.postprocessTree("all", tree);
let extraPublicTrees = [
- createI18nTree(discourseRoot, vendorJs),
parsePluginClientSettings(discourseRoot, vendorJs, app),
funnel(`${discourseRoot}/public/javascripts`, { destDir: "javascripts" }),
applyTerser(
@@ -120,7 +110,6 @@ module.exports = function (defaults) {
),
applyTerser(generateScriptsTree(app)),
applyTerser(discoursePluginsTree),
- testStylesheetTree,
];
const assetCachebuster = process.env["DISCOURSE_ASSET_URL_SALT"] || "";
diff --git a/app/assets/javascripts/discourse/lib/discourse-scss.js b/app/assets/javascripts/discourse/lib/discourse-scss.js
deleted file mode 100644
index a96881dafe7..00000000000
--- a/app/assets/javascripts/discourse/lib/discourse-scss.js
+++ /dev/null
@@ -1,64 +0,0 @@
-const Plugin = require("broccoli-plugin");
-const sass = require("sass");
-const fs = require("fs");
-const concat = require("broccoli-concat");
-
-let builtSet = new Set();
-
-class DiscourseScss extends Plugin {
- constructor(inputNodes, inputFile, options) {
- super(inputNodes, {
- ...options,
- persistentOutput: true,
- });
-
- this.inputFile = inputFile;
- }
-
- build() {
- let file = this.inputPaths[0] + "/" + this.inputFile;
-
- // We could get fancy eventually and do this based on whether the css changes
- // but this is just used for tests right now.
- if (builtSet.has(file)) {
- return;
- }
-
- let deprecationCount = 0;
- let result = sass.renderSync({
- file,
- includePaths: this.inputPaths,
- silenceDeprecations: ["color-functions", "import", "global-builtin"],
- verbose: true, // call warn() for all deprecations
- logger: {
- warn(message, options) {
- if (options.deprecation) {
- deprecationCount += 1;
- } else {
- // eslint-disable-next-line no-console
- console.warn(`\nWARNING: ${message}`);
- }
- },
- },
- });
- if (deprecationCount > 0) {
- // eslint-disable-next-line no-console
- console.warn(
- `\nWARNING: ${deprecationCount} deprecations encountered while compiling scss. (we cannot correct these until the Ruby SCSS pipeline is updated)`
- );
- }
-
- fs.writeFileSync(
- `${this.outputPath}/` + this.inputFile.replace(".scss", ".css"),
- result.css
- );
-
- builtSet.add(file);
- }
-}
-
-module.exports = function scss(path, file) {
- return concat(new DiscourseScss([path], file), {
- outputFile: `assets/${file.replace(".scss", ".css")}`,
- });
-};
diff --git a/app/assets/javascripts/discourse/lib/translation-plugin.js b/app/assets/javascripts/discourse/lib/translation-plugin.js
deleted file mode 100644
index ef626bdf1be..00000000000
--- a/app/assets/javascripts/discourse/lib/translation-plugin.js
+++ /dev/null
@@ -1,137 +0,0 @@
-const Plugin = require("broccoli-plugin");
-const Yaml = require("js-yaml");
-const fs = require("fs");
-const concat = require("broccoli-concat");
-const mergeTrees = require("broccoli-merge-trees");
-const MessageFormat = require("@messageformat/core");
-const deepmerge = require("deepmerge");
-const glob = require("glob");
-const { shouldLoadPlugins } = require("discourse-plugins");
-
-let built = false;
-
-class TranslationPlugin extends Plugin {
- constructor(inputNodes, inputFile, options) {
- super(inputNodes, {
- ...options,
- persistentOutput: true,
- });
-
- this.inputFile = inputFile;
- }
-
- replaceMF(formats, input, path = []) {
- if (!input) {
- return;
- }
-
- Object.keys(input).forEach((key) => {
- let value = input[key];
-
- let subpath = path.concat(key);
- if (typeof value === "object") {
- this.replaceMF(formats, value, subpath);
- } else if (key.endsWith("_MF")) {
- // omit locale.js
- let mfPath = subpath.slice(2).join(".");
- formats[mfPath] = this.mf.compile(value);
- }
- });
- }
-
- build() {
- // We could get fancy eventually and do this based on whether the yaml
- // or vendor files change but in practice we shouldn't need exact up to date
- // translations in admin.
- if (built) {
- return;
- }
-
- let parsed = {};
-
- this.inputPaths.forEach((path) => {
- let file = path + "/" + this.inputFile;
- let yaml = fs.readFileSync(file, { encoding: "UTF-8" });
- let loaded = Yaml.load(yaml, { json: true });
- parsed = deepmerge(parsed, loaded);
- });
-
- let extras = {
- en: {
- admin: parsed.en.admin_js.admin,
- wizard: parsed.en.wizard_js.wizard,
- },
- };
-
- delete parsed.en.admin_js;
- delete parsed.en.wizard_js;
-
- let formats = {};
- this.mf = new MessageFormat("en");
- this.replaceMF(formats, parsed);
- this.replaceMF(formats, extras);
-
- formats = Object.entries(formats).map(([k, v]) => `"${k}": ${v}`);
-
- let contents = `
- (function() {
- I18n.locale = 'en';
- I18n.translations = ${JSON.stringify(parsed)};
- I18n.extras = ${JSON.stringify(extras)};
-
- const Messages = require("@messageformat/runtime/messages").default;
- const { number, plural, select } = require("@messageformat/runtime");
- const { en } = require("@messageformat/runtime/lib/cardinals");
- const msgData = { en: { ${formats.join(",\n")} } };
- const messages = new Messages(msgData, "en");
- messages.defaultLocale = "en";
- I18n._mfMessages = messages;
- })()
- `;
-
- fs.writeFileSync(
- `${this.outputPath}/` + this.inputFile.replace(".yml", ".js"),
- contents
- );
- built = true;
- }
-}
-
-module.exports = function translatePlugin(...params) {
- return new TranslationPlugin(...params);
-};
-
-module.exports.createI18nTree = function (discourseRoot, vendorJs) {
- let translations = [discourseRoot + "/config/locales"];
-
- if (shouldLoadPlugins()) {
- translations = translations.concat(
- glob
- .sync(discourseRoot + "/plugins/*/config/locales/client.en.yml")
- .map((f) => f.replace(/\/client\.en\.yml$/, ""))
- );
- }
-
- let en = new TranslationPlugin(translations, "client.en.yml");
-
- return concat(
- mergeTrees([
- vendorJs,
- discourseRoot + "/app/assets/javascripts/locales",
- en,
- ]),
- {
- inputFiles: [
- "i18n.js",
- "moment.js",
- "moment-timezone-with-data.js",
- "client.en.js",
- ],
- headerFiles: ["i18n.js", "moment.js", "moment-timezone-with-data.js"],
- footerFiles: ["client.en.js"],
- outputFile: `assets/test-i18n.js`,
- }
- );
-};
-
-module.exports.TranslationPlugin = TranslationPlugin;
diff --git a/app/assets/javascripts/discourse/package.json b/app/assets/javascripts/discourse/package.json
index bf8070e2716..55374884ff7 100644
--- a/app/assets/javascripts/discourse/package.json
+++ b/app/assets/javascripts/discourse/package.json
@@ -127,7 +127,6 @@
"pretender": "^3.4.7",
"qunit": "^2.24.1",
"qunit-dom": "^3.4.0",
- "sass": "^1.85.0",
"select-kit": "workspace:1.0.0",
"sinon": "^19.0.2",
"source-map": "^0.7.4",
diff --git a/app/assets/javascripts/discourse/testem.js b/app/assets/javascripts/discourse/testem.js
index d30c4910057..ac54dc31423 100644
--- a/app/assets/javascripts/discourse/testem.js
+++ b/app/assets/javascripts/discourse/testem.js
@@ -1,5 +1,4 @@
const TapReporter = require("testem/lib/reporters/tap_reporter");
-const { shouldLoadPlugins } = require("discourse-plugins");
const fs = require("fs");
const displayUtils = require("testem/lib/utils/displayutils");
const colors = require("@colors/colors/safe");
@@ -177,6 +176,16 @@ if (process.env.TESTEM_FIREFOX_PATH) {
}
const target = `http://127.0.0.1:${process.env.UNICORN_PORT || "3000"}`;
+
+fetch(`${target}/about.json`).catch(() => {
+ // eslint-disable-next-line no-console
+ console.error(
+ colors.red(
+ `Error connecting to Rails server on ${target}. Is it running? Use 'bin/rake qunit:test' or 'plugin:qunit' to start automatically.`
+ )
+ );
+});
+
const themeTestPages = process.env.THEME_TEST_PAGES;
if (themeTestPages) {
@@ -204,20 +213,26 @@ if (themeTestPages) {
});
},
];
-} else if (shouldLoadPlugins()) {
+} else {
// Running with ember cli, but we want to pass through plugin request to Rails
module.exports.proxies = {
+ "/assets/locales/*.js": {
+ target,
+ },
"/assets/plugins/*_extra.js": {
target,
},
"/plugins/": {
target,
},
- "/bootstrap/plugin-css-for-tests.css": {
+ "/bootstrap/": {
target,
},
"/stylesheets/": {
target,
},
+ "/extra-locales/": {
+ target,
+ },
};
}
diff --git a/app/assets/javascripts/discourse/tests/index.html b/app/assets/javascripts/discourse/tests/index.html
index 3ff46ac2e85..3d88c4a1340 100644
--- a/app/assets/javascripts/discourse/tests/index.html
+++ b/app/assets/javascripts/discourse/tests/index.html
@@ -20,7 +20,7 @@
-
+
{{content-for "head-footer"}} {{content-for "test-head-footer"}}
@@ -53,7 +53,10 @@
-
+
+
+
+
@@ -72,6 +75,6 @@
-
+