aboutsummaryrefslogtreecommitdiffstats
path: root/tests
diff options
context:
space:
mode:
authorRoan Kattouw <roan.kattouw@gmail.com>2024-05-01 20:27:22 -0700
committerRoan Kattouw <roan.kattouw@gmail.com>2024-08-22 17:20:24 -0700
commit8a39d83175cbe47a0a66ef95d765bcf8952e5f77 (patch)
treed1ac3e11d1c4fa51af11e416771f8a935c66c1aa /tests
parent38066e53178bbf7df24742c116b1c5b2f92852fa (diff)
downloadmediawikicore-8a39d83175cbe47a0a66ef95d765bcf8952e5f77.tar.gz
mediawikicore-8a39d83175cbe47a0a66ef95d765bcf8952e5f77.zip
Codex: Allow a local development version to be used
Developers can use this to test their local version of Codex with MediaWiki by pointing $wgCodexDevelopmentDir to their local clone of the Codex repo, e.g. $wgCodexDevelopmentDir = '/home/yourname/git/codex'; Setting $wgCodexDevelopmentDir affects where the following things come from: - Codex JS/CSS files for the full library - Codex JS/CSS files for code-split chunks, and the manifest.json file that points to them - Icons retrieved by CodexModule::getIcons() - CSS-only icons imported in Less - Design tokens imported in Less Other changes in this patch: - Add CodexModule::makeFilePath() to centralize the repeated path concatenation. This makes it easier to switch out the regular path for the dev mode path. - Replace all uses of $IP (which is deprecated) and MW_INSTALL_PATH in CodexModule with the BaseDirectory config setting. - Make CodexModule::getIcons() reset its static cache if the path to the icons file changes. Without this, it's impossible to make the unit tests pass. - Move the i18n messages code from the CodexModule constructor to getMessages(). It can't be in the constructor because makeFilePath() doesn't work there (it fails because the Config object hasn't been set up yet). - Add a 'mediawiki.skin.codex' import path so that we can stop hard-coding the path to the Codex mixins file. Without this, we can't make the Codex mixins come from the right place in development mode. - Consider $wgCodexDevelopmentDir in setting the cache key for compiled Less code, since changing this setting can change the output of Less compilation (by changing design tokens, icons or mixins). - Add unit tests for (the non-dev mode behavior of) CodexModule::getIcons() and the i18n message key handling. Bug: T314507 Change-Id: I11c6a81a1ba34fe10f4b1c98bf76f0db40c1ce98
Diffstat (limited to 'tests')
-rw-r--r--tests/phpunit/ResourceLoaderTestCase.php3
-rw-r--r--tests/phpunit/data/less/import-codex-icons-devmode.css3
-rw-r--r--tests/phpunit/data/less/import-codex-tokens-devmode.css3
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex-design-tokens/dist/theme-wikimedia-ui.less1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex-icons/dist/codex-icon-paths.less1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex-icons/dist/codex-icons.json1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/codex.style.css1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/codex.umd.cjs1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/messageKeys.json4
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxButton.css1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxButton.js1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxIcon.css1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxMessage.css1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxMessage.js1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/Icon.js1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/_plugin-vue_export-helper.js1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/constants.js1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/manifest.json838
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useComputedDirection.js1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useComputedLanguage.js1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useIconOnlyButton.js1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useModelWrapper.js1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useSlotContents.js1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useSlotContents2.js1
-rw-r--r--tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useWarnOnce.js1
-rw-r--r--tests/phpunit/data/resourceloader/codex/codex.style.css1
-rw-r--r--tests/phpunit/includes/ResourceLoader/CodexModuleTest.php197
-rw-r--r--tests/phpunit/includes/ResourceLoader/FileModuleTest.php1
-rw-r--r--tests/phpunit/includes/ResourceLoader/LessVarFileModuleTest.php2
-rw-r--r--tests/phpunit/includes/ResourceLoader/ResourceLoaderTest.php29
30 files changed, 1092 insertions, 9 deletions
diff --git a/tests/phpunit/ResourceLoaderTestCase.php b/tests/phpunit/ResourceLoaderTestCase.php
index ca58ace062e9..e137207b6f87 100644
--- a/tests/phpunit/ResourceLoaderTestCase.php
+++ b/tests/phpunit/ResourceLoaderTestCase.php
@@ -95,6 +95,9 @@ abstract class ResourceLoaderTestCase extends MediaWikiIntegrationTestCase {
MainConfigNames::ScriptPath => '/w',
MainConfigNames::Script => '/w/index.php',
MainConfigNames::ResourceLoaderEnableJSProfiler => false,
+
+ // For CodexModule
+ MainConfigNames::CodexDevelopmentDir => null,
];
}
diff --git a/tests/phpunit/data/less/import-codex-icons-devmode.css b/tests/phpunit/data/less/import-codex-icons-devmode.css
new file mode 100644
index 000000000000..0b5b300cc955
--- /dev/null
+++ b/tests/phpunit/data/less/import-codex-icons-devmode.css
@@ -0,0 +1,3 @@
+.foo:before {
+ content: 'test add icon';
+}
diff --git a/tests/phpunit/data/less/import-codex-tokens-devmode.css b/tests/phpunit/data/less/import-codex-tokens-devmode.css
new file mode 100644
index 000000000000..9b5c1c9efddd
--- /dev/null
+++ b/tests/phpunit/data/less/import-codex-tokens-devmode.css
@@ -0,0 +1,3 @@
+.foo {
+ width: 'test size 100';
+}
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex-design-tokens/dist/theme-wikimedia-ui.less b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex-design-tokens/dist/theme-wikimedia-ui.less
new file mode 100644
index 000000000000..620ff2dec784
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex-design-tokens/dist/theme-wikimedia-ui.less
@@ -0,0 +1 @@
+@size-100: 'test size 100';
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex-icons/dist/codex-icon-paths.less b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex-icons/dist/codex-icon-paths.less
new file mode 100644
index 000000000000..1afe34a26730
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex-icons/dist/codex-icon-paths.less
@@ -0,0 +1 @@
+@cdx-icon-add: 'test add icon';
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex-icons/dist/codex-icons.json b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex-icons/dist/codex-icons.json
new file mode 100644
index 000000000000..4e710df0405d
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex-icons/dist/codex-icons.json
@@ -0,0 +1 @@
+{"cdxIconAdd":"test add icon","cdxIconNext":"test next icon"} \ No newline at end of file
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/codex.style.css b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/codex.style.css
new file mode 100644
index 000000000000..7bf9000dad5f
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/codex.style.css
@@ -0,0 +1 @@
+/* Placeholder for tests */
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/codex.umd.cjs b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/codex.umd.cjs
new file mode 100644
index 000000000000..335461eb9bab
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/codex.umd.cjs
@@ -0,0 +1 @@
+// Placeholder file for tests \ No newline at end of file
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/messageKeys.json b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/messageKeys.json
new file mode 100644
index 000000000000..feeace382b95
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/messageKeys.json
@@ -0,0 +1,4 @@
+[
+ "cdx-test-message-1",
+ "cdx-test-message-2"
+] \ No newline at end of file
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxButton.css b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxButton.css
new file mode 100644
index 000000000000..5d75453cbacf
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxButton.css
@@ -0,0 +1 @@
+.cdx-button{box-sizing:border-box;min-width:32px;min-height:32px;max-width:28em;margin:0;border-width:1px;border-style:solid;border-radius:2px;padding-right:11px;padding-left:11px;font-family:inherit;font-size:inherit;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-transform:none;transition-property:background-color,color,border-color,box-shadow;transition-duration:.1s}.cdx-button--size-large{min-width:44px;min-height:44px;padding-right:15px;padding-left:15px}.cdx-button--icon-only{padding-right:5px;padding-left:5px}.cdx-button--icon-only.cdx-button--size-large{padding-right:11px;padding-left:11px}.cdx-button::-moz-focus-inner{border:0;padding:0}.cdx-button .cdx-button__icon,.cdx-button .cdx-icon{vertical-align:middle}.cdx-button .cdx-icon{color:inherit}.cdx-button--fake-button{display:inline-flex;align-items:center;justify-content:center}.cdx-button--fake-button,.cdx-button--fake-button:hover,.cdx-button--fake-button:focus{text-decoration:none}.cdx-button--fake-button:not(.cdx-button--icon-only) .cdx-button__icon{margin-right:4px}.cdx-button:enabled,.cdx-button.cdx-button--fake-button--enabled{background-color:#f8f9fa;color:#202122;border-color:#a2a9b1}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled .cdx-button__icon{background-color:#202122}}.cdx-button:enabled:hover,.cdx-button.cdx-button--fake-button--enabled:hover{background-color:#fff;color:#404244;cursor:pointer}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled:hover .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled:hover .cdx-button__icon{background-color:#404244}}.cdx-button:enabled:active,.cdx-button.cdx-button--fake-button--enabled:active,.cdx-button:enabled.cdx-button--is-active,.cdx-button.cdx-button--fake-button--enabled.cdx-button--is-active{background-color:#eaecf0;color:#000;border-color:#72777d}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled:active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled:active .cdx-button__icon,.cdx-button:enabled.cdx-button--is-active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--is-active .cdx-button__icon{background-color:#000}}.cdx-button:enabled:focus,.cdx-button.cdx-button--fake-button--enabled:focus{outline:1px solid transparent}.cdx-button:enabled:focus:not(:active):not(.cdx-button--is-active),.cdx-button.cdx-button--fake-button--enabled:focus:not(:active):not(.cdx-button--is-active){border-color:#36c;box-shadow:inset 0 0 0 1px #36c}.cdx-button:enabled.cdx-button--action-progressive,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-progressive{color:#36c}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--action-progressive .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-progressive .cdx-button__icon{background-color:#36c}}.cdx-button:enabled.cdx-button--action-progressive:hover,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-progressive:hover{color:#447ff5;border-color:#447ff5}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--action-progressive:hover .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-progressive:hover .cdx-button__icon{background-color:#447ff5}}.cdx-button:enabled.cdx-button--action-progressive:active,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-progressive:active,.cdx-button:enabled.cdx-button--action-progressive.cdx-button--is-active,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-progressive.cdx-button--is-active{background-color:#eaf3ff;color:#2a4b8d;border-color:#2a4b8d}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--action-progressive:active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-progressive:active .cdx-button__icon,.cdx-button:enabled.cdx-button--action-progressive.cdx-button--is-active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-progressive.cdx-button--is-active .cdx-button__icon{background-color:#2a4b8d}}.cdx-button:enabled.cdx-button--action-destructive,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-destructive{color:#d73333}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--action-destructive .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-destructive .cdx-button__icon{background-color:#d73333}}.cdx-button:enabled.cdx-button--action-destructive:hover,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-destructive:hover{color:#ff4242;border-color:#ff4242}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--action-destructive:hover .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-destructive:hover .cdx-button__icon{background-color:#ff4242}}.cdx-button:enabled.cdx-button--action-destructive:active,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-destructive:active,.cdx-button:enabled.cdx-button--action-destructive.cdx-button--is-active,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-destructive.cdx-button--is-active{background-color:#fee7e6;color:#b32424;border-color:#b32424}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--action-destructive:active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-destructive:active .cdx-button__icon,.cdx-button:enabled.cdx-button--action-destructive.cdx-button--is-active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-destructive.cdx-button--is-active .cdx-button__icon{background-color:#b32424}}.cdx-button:enabled.cdx-button--action-destructive:focus:not(:active):not(.cdx-button--is-active),.cdx-button.cdx-button--fake-button--enabled.cdx-button--action-destructive:focus:not(:active):not(.cdx-button--is-active){border-color:#d73333;box-shadow:inset 0 0 0 1px #d73333}.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-progressive,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-progressive{background-color:#36c;color:#fff;border-color:#36c}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-progressive .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-progressive .cdx-button__icon{background-color:#fff}}.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-progressive:hover,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-progressive:hover{background-color:#447ff5;border-color:#447ff5}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-progressive:hover .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-progressive:hover .cdx-button__icon{background-color:#fff}}.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-progressive:active,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-progressive:active,.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-progressive.cdx-button--is-active,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-progressive.cdx-button--is-active{background-color:#2a4b8d;border-color:#2a4b8d}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-progressive:active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-progressive:active .cdx-button__icon,.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-progressive.cdx-button--is-active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-progressive.cdx-button--is-active .cdx-button__icon{background-color:#fff}}.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-progressive:focus:not(:active):not(.cdx-button--is-active),.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-progressive:focus:not(:active):not(.cdx-button--is-active){border-color:#36c;box-shadow:inset 0 0 0 1px #36c,inset 0 0 0 2px #fff}.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-destructive,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-destructive{background-color:#d73333;color:#fff;border-color:#d73333}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-destructive .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-destructive .cdx-button__icon{background-color:#fff}}.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-destructive:hover,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-destructive:hover{background-color:#ff4242;border-color:#ff4242}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-destructive:hover .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-destructive:hover .cdx-button__icon{background-color:#fff}}.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-destructive:active,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-destructive:active,.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-destructive.cdx-button--is-active,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-destructive.cdx-button--is-active{background-color:#b32424;border-color:#b32424}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-destructive:active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-destructive:active .cdx-button__icon,.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-destructive.cdx-button--is-active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-destructive.cdx-button--is-active .cdx-button__icon{background-color:#fff}}.cdx-button:enabled.cdx-button--weight-primary.cdx-button--action-destructive:focus:not(:active):not(.cdx-button--is-active),.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-primary.cdx-button--action-destructive:focus:not(:active):not(.cdx-button--is-active){border-color:#d73333;box-shadow:inset 0 0 0 1px #d73333,inset 0 0 0 2px #fff}.cdx-button:enabled.cdx-button--weight-quiet,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet{background-color:rgba(255,255,255,0);border-color:transparent}.cdx-button:enabled.cdx-button--weight-quiet:hover,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet:hover{background-color:rgba(0,24,73,.027)}.cdx-button:enabled.cdx-button--weight-quiet:active,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet:active,.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--is-active,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--is-active{background-color:rgba(0,24,73,.082);color:#000;border-color:#72777d}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--weight-quiet:active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet:active .cdx-button__icon,.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--is-active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--is-active .cdx-button__icon{background-color:#000}}.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-progressive,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-progressive{color:#36c}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-progressive .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-progressive .cdx-button__icon{background-color:#36c}}.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-progressive:hover,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-progressive:hover{background-color:#eaf3ff;color:#447ff5}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-progressive:hover .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-progressive:hover .cdx-button__icon{background-color:#447ff5}}.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-progressive:active,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-progressive:active,.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-progressive.cdx-button--is-active,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-progressive.cdx-button--is-active{background-color:#2a4b8d;color:#fff;border-color:#2a4b8d}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-progressive:active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-progressive:active .cdx-button__icon,.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-progressive.cdx-button--is-active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-progressive.cdx-button--is-active .cdx-button__icon{background-color:#fff}}.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-destructive,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-destructive{color:#d73333}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-destructive .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-destructive .cdx-button__icon{background-color:#d73333}}.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-destructive:hover,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-destructive:hover{background-color:#fee7e6;color:#ff4242}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-destructive:hover .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-destructive:hover .cdx-button__icon{background-color:#ff4242}}.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-destructive:active,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-destructive:active,.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-destructive.cdx-button--is-active,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-destructive.cdx-button--is-active{background-color:#b32424;color:#fff;border-color:#b32424}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-destructive:active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-destructive:active .cdx-button__icon,.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-destructive.cdx-button--is-active .cdx-button__icon,.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-destructive.cdx-button--is-active .cdx-button__icon{background-color:#fff}}.cdx-button:enabled.cdx-button--weight-quiet.cdx-button--action-destructive:focus:not(:active):not(.cdx-button--is-active),.cdx-button.cdx-button--fake-button--enabled.cdx-button--weight-quiet.cdx-button--action-destructive:focus:not(:active):not(.cdx-button--is-active){border-color:#d73333;box-shadow:inset 0 0 0 1px #d73333}.cdx-button:disabled,.cdx-button.cdx-button--fake-button--disabled{background-color:#c8ccd1;color:#fff;border-color:transparent}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:disabled .cdx-button__icon,.cdx-button.cdx-button--fake-button--disabled .cdx-button__icon{background-color:#fff}}.cdx-button:disabled.cdx-button--weight-quiet,.cdx-button.cdx-button--fake-button--disabled.cdx-button--weight-quiet{background-color:rgba(255,255,255,0);color:#72777d}@supports ((-webkit-mask-image: none) or (mask-image: none)){.cdx-button:disabled.cdx-button--weight-quiet .cdx-button__icon,.cdx-button.cdx-button--fake-button--disabled.cdx-button--weight-quiet .cdx-button__icon{background-color:#72777d}}
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxButton.js b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxButton.js
new file mode 100644
index 000000000000..fc2b49b93ff4
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxButton.js
@@ -0,0 +1 @@
+"use strict";const e=require("vue"),o=require("./constants.js"),c=require("./useIconOnlyButton.js"),d=require("./_plugin-vue_export-helper.js");require("./useSlotContents.js");require("./useWarnOnce.js");const p=o.makeStringTypeValidator(o.ButtonActions),v=o.makeStringTypeValidator(o.ButtonWeights),y=o.makeStringTypeValidator(o.ButtonSizes),f=e.defineComponent({name:"CdxButton",props:{action:{type:String,default:"default",validator:p},weight:{type:String,default:"normal",validator:v},size:{type:String,default:"medium",validator:y}},emits:["click"],setup(t,{emit:n,slots:r,attrs:a}){const l=c.useIconOnlyButton(r.default,a,"CdxButton"),s=e.ref(!1);return{rootClasses:e.computed(()=>({["cdx-button--action-".concat(t.action)]:!0,["cdx-button--weight-".concat(t.weight)]:!0,["cdx-button--size-".concat(t.size)]:!0,"cdx-button--framed":t.weight!=="quiet","cdx-button--icon-only":l.value,"cdx-button--is-active":s.value})),onClick:u=>{n("click",u)},setActive:u=>{s.value=u}}}});function g(t,n,r,a,l,s){return e.openBlock(),e.createElementBlock("button",{class:e.normalizeClass(["cdx-button",t.rootClasses]),onClick:n[0]||(n[0]=(...i)=>t.onClick&&t.onClick(...i)),onKeydown:n[1]||(n[1]=e.withKeys(i=>t.setActive(!0),["space","enter"])),onKeyup:n[2]||(n[2]=e.withKeys(i=>t.setActive(!1),["space","enter"]))},[e.renderSlot(t.$slots,"default")],34)}const m=d._export_sfc(f,[["render",g]]);module.exports=m;
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxIcon.css b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxIcon.css
new file mode 100644
index 000000000000..f55ced873eb4
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxIcon.css
@@ -0,0 +1 @@
+.cdx-icon{color:#202122;display:inline-flex;align-items:center;justify-content:center;vertical-align:text-bottom}.cdx-icon svg{fill:currentcolor;width:100%;height:100%}.cdx-icon--x-small{min-width:12px;min-height:12px;width:.75em;height:.75em}.cdx-icon--small{min-width:16px;min-height:16px;width:1em;height:1em}.cdx-icon--medium{min-width:20px;min-height:20px;width:1.25em;height:1.25em}.cdx-icon--flipped svg{transform:scaleX(-1)}
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxMessage.css b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxMessage.css
new file mode 100644
index 000000000000..4bae45e2bf91
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxMessage.css
@@ -0,0 +1 @@
+.cdx-message{background-color:#eaecf0;color:#202122;display:flex;align-items:flex-start;position:relative;border:1px solid #54595d;padding:16px}@media screen and (min-width: 640px){.cdx-message{padding-right:24px;padding-left:24px}}.cdx-message .cdx-message__icon{background-position:center;background-repeat:no-repeat;background-size:max(1.25em,20px);min-width:20px;min-height:20px;width:1.25em;height:1.25em;display:inline-block;vertical-align:text-bottom;background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" fill="%23202122"><path d="M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zM9 5h2v2H9zm0 4h2v6H9z"/></svg>')}.cdx-message .cdx-message__icon:lang(ar){background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" fill="%23202122"><path d="M8 19a1 1 0 001 1h2a1 1 0 001-1v-1H8zm9-12a7 7 0 10-12 4.9S7 14 7 15v1a1 1 0 001 1h4a1 1 0 001-1v-1c0-1 2-3.1 2-3.1A7 7 0 0017 7z"/></svg>')}.cdx-message--warning{background-color:#fef6e7;border-color:#ac6600}.cdx-message--warning .cdx-message__icon{background-position:center;background-repeat:no-repeat;background-size:max(1.25em,20px);min-width:20px;min-height:20px;width:1.25em;height:1.25em;display:inline-block;vertical-align:text-bottom;background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" fill="%23edab00"><path d="M11.53 2.3A1.85 1.85 0 0010 1.21 1.85 1.85 0 008.48 2.3L.36 16.36C-.48 17.81.21 19 1.88 19h16.24c1.67 0 2.36-1.19 1.52-2.64zM11 16H9v-2h2zm0-4H9V6h2z"/></svg>')}.cdx-message--warning .cdx-message__icon--vue{color:#edab00}.cdx-message--error{background-color:#fee7e6;border-color:#b32424}.cdx-message--error .cdx-message__icon{background-position:center;background-repeat:no-repeat;background-size:max(1.25em,20px);min-width:20px;min-height:20px;width:1.25em;height:1.25em;display:inline-block;vertical-align:text-bottom;background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" fill="%23d73333"><path d="M13.728 1H6.272L1 6.272v7.456L6.272 19h7.456L19 13.728V6.272zM11 15H9v-2h2zm0-4H9V5h2z"/></svg>')}.cdx-message--error .cdx-message__icon--vue{color:#d73333}.cdx-message--success{background-color:#d5fdf4;border-color:#096450}.cdx-message--success .cdx-message__icon{background-position:center;background-repeat:no-repeat;background-size:max(1.25em,20px);min-width:20px;min-height:20px;width:1.25em;height:1.25em;display:inline-block;vertical-align:text-bottom;background-image:url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="20" height="20" viewBox="0 0 20 20" fill="%2314866d"><path d="M10 20a10 10 0 010-20 10 10 0 110 20Zm-2-5 9-8.5L15.5 5 8 12 4.5 8.5 3 10l5 5Z"/></svg>')}.cdx-message--success .cdx-message__icon--vue{color:#14866d}.cdx-message--user-dismissable{padding-right:48px}@media screen and (min-width: 640px){.cdx-message--user-dismissable{padding-right:56px}}.cdx-message--inline{background-color:rgba(255,255,255,0);border:0;padding:0;font-weight:700}.cdx-message--inline.cdx-message--error{color:#d73333}.cdx-message--inline.cdx-message--success{color:#14866d}.cdx-message .cdx-message__icon,.cdx-message .cdx-message__icon--vue{height:1.5em}.cdx-message__content{word-wrap:break-word;-webkit-hyphens:auto;-ms-hyphens:auto;hyphens:auto;align-self:center;flex-grow:1;margin-left:8px}.cdx-message__content,.cdx-message__content>*{line-height:1.6}.cdx-message__content>*:first-child{margin-top:0;padding-top:0}.cdx-message__content>*:last-child{margin-bottom:0;padding-bottom:0}.cdx-message__dismiss-button{position:absolute;top:12px;right:16px;padding:5px;line-height:0}@media screen and (min-width: 640px){.cdx-message__dismiss-button{right:8px}}.cdx-message+.cdx-message{margin-top:8px}.cdx-message-enter-active,.cdx-message-leave-active-system{transition-property:opacity;transition-duration:.25s;transition-timing-function:ease}.cdx-message-leave-active-user{transition-property:opacity;transition-duration:.25s;transition-timing-function:ease-out}.cdx-message-enter-from,.cdx-message-leave-to{opacity:0}
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxMessage.js b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxMessage.js
new file mode 100644
index 000000000000..91dad2e8d268
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/CdxMessage.js
@@ -0,0 +1 @@
+"use strict";const s=require("vue"),t=require("./Icon.js"),m=require("./CdxButton.js"),f=require("./constants.js"),v=require("./_plugin-vue_export-helper.js");require("./useIconOnlyButton.js");require("./useSlotContents.js");require("./useWarnOnce.js");const p={notice:t.K3,error:t.e3,warning:t.m5,success:t.D7},y=s.defineComponent({name:"CdxMessage",components:{CdxButton:m,CdxIcon:t.CdxIcon},props:{type:{type:String,default:"notice",validator:f.statusTypeValidator},inline:{type:Boolean,default:!1},icon:{type:[String,Object],default:null},fadeIn:{type:Boolean,default:!1},dismissButtonLabel:{type:String,default:""},autoDismiss:{type:[Boolean,Number],default:!1,validator:e=>typeof e=="boolean"||typeof e=="number"&&e>0}},emits:["user-dismissed","auto-dismissed"],setup(e,{emit:o}){const n=s.ref(!1),l=s.computed(()=>e.inline===!1&&e.dismissButtonLabel.length>0),c=s.computed(()=>e.autoDismiss===!1||e.type==="error"?!1:e.autoDismiss===!0?4e3:e.autoDismiss),u=s.computed(()=>({"cdx-message--inline":e.inline,"cdx-message--block":!e.inline,"cdx-message--user-dismissable":l.value,["cdx-message--".concat(e.type)]:!0})),i=s.computed(()=>e.icon&&e.type==="notice"?e.icon:p[e.type]),a=s.ref("");function r(d){n.value||(a.value=d==="user-dismissed"?"cdx-message-leave-active-user":"cdx-message-leave-active-system",n.value=!0,o(d))}return s.onMounted(()=>{e.type==="error"&&e.autoDismiss!==!1?s.warn('CdxMessage: Message with type="error" cannot use auto-dismiss'):c.value&&setTimeout(()=>r("auto-dismissed"),c.value)}),{dismissed:n,userDismissable:l,rootClasses:u,leaveActiveClass:a,computedIcon:i,onDismiss:r,cdxIconClose:t.Q5}}});const g=["aria-live","role"],C={class:"cdx-message__content"};function b(e,o,n,l,c,u){const i=s.resolveComponent("cdx-icon"),a=s.resolveComponent("cdx-button");return s.openBlock(),s.createBlock(s.Transition,{name:"cdx-message",appear:e.fadeIn,"leave-active-class":e.leaveActiveClass},{default:s.withCtx(()=>[e.dismissed?s.createCommentVNode("",!0):(s.openBlock(),s.createElementBlock("div",{key:0,class:s.normalizeClass(["cdx-message",e.rootClasses]),"aria-live":e.type!=="error"?"polite":void 0,role:e.type==="error"?"alert":void 0},[s.createVNode(i,{class:"cdx-message__icon--vue",icon:e.computedIcon},null,8,["icon"]),s.createElementVNode("div",C,[s.renderSlot(e.$slots,"default")]),e.userDismissable?(s.openBlock(),s.createBlock(a,{key:0,class:"cdx-message__dismiss-button",weight:"quiet",type:"button","aria-label":e.dismissButtonLabel,onClick:o[0]||(o[0]=r=>e.onDismiss("user-dismissed"))},{default:s.withCtx(()=>[s.createVNode(i,{icon:e.cdxIconClose,"icon-label":e.dismissButtonLabel},null,8,["icon","icon-label"])]),_:1},8,["aria-label"])):s.createCommentVNode("",!0)],10,g))]),_:3},8,["appear","leave-active-class"])}const _=v._export_sfc(y,[["render",b]]);module.exports=_;
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/Icon.js b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/Icon.js
new file mode 100644
index 000000000000..32e59aaac283
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/Icon.js
@@ -0,0 +1 @@
+"use strict";const t=require("vue"),d=require("./constants.js"),h=require("./_plugin-vue_export-helper.js"),m='<path d="M11.53 2.3A1.85 1.85 0 0010 1.21 1.85 1.85 0 008.48 2.3L.36 16.36C-.48 17.81.21 19 1.88 19h16.24c1.67 0 2.36-1.19 1.52-2.64zM11 16H9v-2h2zm0-4H9V6h2z"/>',v='<path d="M12.43 14.34A5 5 0 0110 15a5 5 0 113.95-2L17 16.09V3a2 2 0 00-2-2H5a2 2 0 00-2 2v14a2 2 0 002 2h10a2 2 0 001.45-.63z"/><circle cx="10" cy="10" r="3"/>',g='<path d="M10 0a10 10 0 1010 10A10 10 0 0010 0zm5.66 14.24-1.41 1.41L10 11.41l-4.24 4.25-1.42-1.42L8.59 10 4.34 5.76l1.42-1.42L10 8.59l4.24-4.24 1.41 1.41L11.41 10z"/>',f='<path d="m4.34 2.93 12.73 12.73-1.41 1.41L2.93 4.35z"/><path d="M17.07 4.34 4.34 17.07l-1.41-1.41L15.66 2.93z"/>',z='<path d="M13.728 1H6.272L1 6.272v7.456L6.272 19h7.456L19 13.728V6.272zM11 15H9v-2h2zm0-4H9V5h2z"/>',M='<path d="m17.5 4.75-7.5 7.5-7.5-7.5L1 6.25l9 9 9-9z"/>',L='<path d="M19 3H1v14h18zM3 14l3.5-4.5 2.5 3L12.5 8l4.5 6z"/><path d="M19 5H1V3h18zm0 12H1v-2h18z"/>',y='<path d="M8 19a1 1 0 001 1h2a1 1 0 001-1v-1H8zm9-12a7 7 0 10-12 4.9S7 14 7 15v1a1 1 0 001 1h4a1 1 0 001-1v-1c0-1 2-3.1 2-3.1A7 7 0 0017 7z"/>',C='<path d="M10 0C4.477 0 0 4.477 0 10s4.477 10 10 10 10-4.477 10-10S15.523 0 10 0zM9 5h2v2H9zm0 4h2v6H9z"/>',k='<path d="M7 1 5.6 2.5 13 10l-7.4 7.5L7 19l9-9z"/>',_='<path d="m4 10 9 9 1.4-1.5L7 10l7.4-7.5L13 1z"/>',H='<path d="M12.2 13.6a7 7 0 111.4-1.4l5.4 5.4-1.4 1.4zM3 8a5 5 0 1010 0A5 5 0 003 8z"/>',S='<path d="M10 20a10 10 0 010-20 10 10 0 110 20Zm-2-5 9-8.5L15.5 5 8 12 4.5 8.5 3 10l5 5Z"/>',w=m,x=v,B=g,E=f,V=z,D=M,F=L,A={langCodeMap:{ar:y},default:C},I={ltr:k,shouldFlip:!0},$={ltr:_,shouldFlip:!0},b=H,K=S;function q(e,o,n){if(typeof e=="string"||"path"in e)return e;if("shouldFlip"in e)return e.ltr;if("rtl"in e)return n==="rtl"?e.rtl:e.ltr;const l=o in e.langCodeMap?e.langCodeMap[o]:e.default;return typeof l=="string"||"path"in l?l:l.ltr}function T(e,o){if(typeof e=="string")return!1;if("langCodeMap"in e){const n=o in e.langCodeMap?e.langCodeMap[o]:e.default;if(typeof n=="string")return!1;e=n}if("shouldFlipExceptions"in e&&Array.isArray(e.shouldFlipExceptions)){const n=e.shouldFlipExceptions.indexOf(o);return n===void 0||n===-1}return"shouldFlip"in e?e.shouldFlip:!1}function u(e){const o=t.ref(null);return t.onMounted(()=>{const n=window.getComputedStyle(e.value).direction;o.value=n==="ltr"||n==="rtl"?n:null}),o}function Z(e){const o=t.ref("");return t.onMounted(()=>{let n=e.value;for(;n&&n.lang==="";)n=n.parentElement;o.value=n?n.lang:null}),o}const J=d.makeStringTypeValidator(d.IconSizes),O=t.defineComponent({name:"CdxIcon",props:{icon:{type:[String,Object],required:!0},iconLabel:{type:String,default:""},lang:{type:String,default:null},dir:{type:String,default:null},size:{type:String,default:"medium",validator:J}},setup(e){const o=t.ref(),n=u(o),l=Z(o),r=t.computed(()=>e.dir||n.value),i=t.computed(()=>e.lang||l.value),s=t.computed(()=>({"cdx-icon--flipped":r.value==="rtl"&&i.value!==null&&T(e.icon,i.value),["cdx-icon--".concat(e.size)]:!0})),a=t.computed(()=>q(e.icon,i.value||"",r.value||"ltr")),c=t.computed(()=>typeof a.value=="string"?a.value:""),p=t.computed(()=>typeof a.value!="string"?a.value.path:"");return{rootElement:o,rootClasses:s,iconSvg:c,iconPath:p}}});const P=["aria-hidden"],Q={key:0},j=["innerHTML"],N=["d"];function R(e,o,n,l,r,i){return t.openBlock(),t.createElementBlock("span",{ref:"rootElement",class:t.normalizeClass(["cdx-icon",e.rootClasses])},[(t.openBlock(),t.createElementBlock("svg",{xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink",width:"20",height:"20",viewBox:"0 0 20 20","aria-hidden":e.iconLabel?void 0:!0},[e.iconLabel?(t.openBlock(),t.createElementBlock("title",Q,t.toDisplayString(e.iconLabel),1)):t.createCommentVNode("",!0),e.iconSvg?(t.openBlock(),t.createElementBlock("g",{key:1,innerHTML:e.iconSvg},null,8,j)):(t.openBlock(),t.createElementBlock("path",{key:2,d:e.iconPath},null,8,N))],8,P))],2)}const U=h._export_sfc(O,[["render",R]]);exports.CdxIcon=U;exports.D3=F;exports.D7=K;exports.J6=I;exports.K3=A;exports.K5=B;exports.Q5=E;exports.V7=b;exports.e3=V;exports.k5=x;exports.m5=w;exports.s7=$;exports.useComputedDirection=u;exports.z3=D;
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/_plugin-vue_export-helper.js b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/_plugin-vue_export-helper.js
new file mode 100644
index 000000000000..dc82924e17cc
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/_plugin-vue_export-helper.js
@@ -0,0 +1 @@
+"use strict";const e=(t,o)=>{const c=t.__vccOpts||t;for(const[r,s]of o)c[r]=s;return c};exports._export_sfc=e;
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/constants.js b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/constants.js
new file mode 100644
index 000000000000..c9ee9b19096d
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/constants.js
@@ -0,0 +1 @@
+"use strict";function t(n){return e=>typeof e=="string"&&n.indexOf(e)!==-1}const s="cdx",o=["default","progressive","destructive"],i=["normal","primary","quiet"],a=["medium","large"],l=["x-small","small","medium"],r=["notice","warning","error","success"],d=t(r),c=["text","search","number","email","month","password","tel","url","week","date","datetime-local","time"],u=["default","error"],y=120,m=500,p="cdx-menu-footer-item",b=Symbol("CdxTabs"),S=Symbol("CdxActiveTab"),I=Symbol("CdxFieldInputId"),T=Symbol("CdxFieldDescriptionId"),x=Symbol("CdxFieldStatus"),K=Symbol("CdxDisabled"),F="".concat(s,"-no-invert");exports.ActiveTabKey=S;exports.ButtonActions=o;exports.ButtonSizes=a;exports.ButtonWeights=i;exports.DebounceInterval=y;exports.DisabledKey=K;exports.FieldDescriptionIdKey=T;exports.FieldInputIdKey=I;exports.FieldStatusKey=x;exports.IconSizes=l;exports.LibraryPrefix=s;exports.MenuFooterValue=p;exports.NoInvertClass=F;exports.PendingDelay=m;exports.TabsKey=b;exports.TextInputTypes=c;exports.ValidationStatusTypes=u;exports.makeStringTypeValidator=t;exports.statusTypeValidator=d;
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/manifest.json b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/manifest.json
new file mode 100644
index 000000000000..7a0a605413e7
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/manifest.json
@@ -0,0 +1,838 @@
+{
+ "Icon.css": {
+ "file": "CdxIcon.css",
+ "src": "Icon.css"
+ },
+ "_Icon.js": {
+ "css": [
+ "CdxIcon.css"
+ ],
+ "file": "Icon.js",
+ "imports": [
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "_constants.js",
+ "__plugin-vue_export-helper.js"
+ ]
+ },
+ "__plugin-vue_export-helper.js": {
+ "file": "_plugin-vue_export-helper.js"
+ },
+ "_buttonHelpers.js": {
+ "file": "buttonHelpers.js"
+ },
+ "_constants.js": {
+ "file": "constants.js"
+ },
+ "_useIconOnlyButton.js": {
+ "file": "useIconOnlyButton.js",
+ "imports": [
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts"
+ ]
+ },
+ "_useLabelChecker.js": {
+ "file": "useLabelChecker.js",
+ "imports": [
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts"
+ ]
+ },
+ "_useSlotContents2.js": {
+ "file": "useSlotContents2.js"
+ },
+ "src/components/accordion/Accordion.css": {
+ "file": "CdxAccordion.css",
+ "src": "src/components/accordion/Accordion.css"
+ },
+ "src/components/accordion/Accordion.vue": {
+ "css": [
+ "CdxAccordion.css"
+ ],
+ "file": "CdxAccordion.js",
+ "imports": [
+ "_Icon.js",
+ "src/components/button/Button.vue",
+ "src/composables/useGeneratedId.ts",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "_constants.js",
+ "_useIconOnlyButton.js",
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/accordion/Accordion.vue"
+ },
+ "src/components/button-group/ButtonGroup.css": {
+ "file": "CdxButtonGroup.css",
+ "src": "src/components/button-group/ButtonGroup.css"
+ },
+ "src/components/button-group/ButtonGroup.vue": {
+ "css": [
+ "CdxButtonGroup.css"
+ ],
+ "file": "CdxButtonGroup.js",
+ "imports": [
+ "_buttonHelpers.js",
+ "src/components/button/Button.vue",
+ "_Icon.js",
+ "__plugin-vue_export-helper.js",
+ "_constants.js",
+ "_useIconOnlyButton.js",
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/button-group/ButtonGroup.vue"
+ },
+ "src/components/button/Button.css": {
+ "file": "CdxButton.css",
+ "src": "src/components/button/Button.css"
+ },
+ "src/components/button/Button.vue": {
+ "css": [
+ "CdxButton.css"
+ ],
+ "file": "CdxButton.js",
+ "imports": [
+ "_constants.js",
+ "_useIconOnlyButton.js",
+ "__plugin-vue_export-helper.js",
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/button/Button.vue"
+ },
+ "src/components/card/Card.css": {
+ "file": "CdxCard.css",
+ "src": "src/components/card/Card.css"
+ },
+ "src/components/card/Card.vue": {
+ "css": [
+ "CdxCard.css"
+ ],
+ "file": "CdxCard.js",
+ "imports": [
+ "_Icon.js",
+ "src/components/thumbnail/Thumbnail.vue",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "_constants.js"
+ ],
+ "isEntry": true,
+ "src": "src/components/card/Card.vue"
+ },
+ "src/components/checkbox/Checkbox.css": {
+ "file": "CdxCheckbox.css",
+ "src": "src/components/checkbox/Checkbox.css"
+ },
+ "src/components/checkbox/Checkbox.vue": {
+ "css": [
+ "CdxCheckbox.css"
+ ],
+ "file": "CdxCheckbox.js",
+ "imports": [
+ "src/components/label/Label.vue",
+ "_useLabelChecker.js",
+ "src/composables/useModelWrapper.ts",
+ "src/composables/useGeneratedId.ts",
+ "src/composables/useFieldData.ts",
+ "_constants.js",
+ "__plugin-vue_export-helper.js",
+ "_Icon.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "src/composables/useSplitAttributes.ts",
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts",
+ "src/composables/useComputedDisabled.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/checkbox/Checkbox.vue"
+ },
+ "src/components/chip-input/ChipInput.css": {
+ "file": "CdxChipInput.css",
+ "src": "src/components/chip-input/ChipInput.css"
+ },
+ "src/components/chip-input/ChipInput.vue": {
+ "css": [
+ "CdxChipInput.css"
+ ],
+ "file": "CdxChipInput.js",
+ "imports": [
+ "src/components/button/Button.vue",
+ "_Icon.js",
+ "__plugin-vue_export-helper.js",
+ "_constants.js",
+ "src/composables/useSplitAttributes.ts",
+ "src/composables/useFieldData.ts",
+ "src/composables/useComputedDirection.ts",
+ "_useIconOnlyButton.js",
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts",
+ "src/composables/useComputedLanguage.ts",
+ "src/composables/useComputedDisabled.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/chip-input/ChipInput.vue"
+ },
+ "src/components/combobox/Combobox.css": {
+ "file": "CdxCombobox.css",
+ "src": "src/components/combobox/Combobox.css"
+ },
+ "src/components/combobox/Combobox.vue": {
+ "css": [
+ "CdxCombobox.css"
+ ],
+ "file": "CdxCombobox.js",
+ "imports": [
+ "_Icon.js",
+ "src/components/button/Button.vue",
+ "src/components/menu/Menu.vue",
+ "src/components/text-input/TextInput.vue",
+ "src/composables/useModelWrapper.ts",
+ "src/composables/useGeneratedId.ts",
+ "src/composables/useSplitAttributes.ts",
+ "src/composables/useFieldData.ts",
+ "src/composables/useFloatingMenu.ts",
+ "_constants.js",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "_useIconOnlyButton.js",
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts",
+ "src/components/menu-item/MenuItem.vue",
+ "src/components/thumbnail/Thumbnail.vue",
+ "src/components/search-result-title/SearchResultTitle.vue",
+ "src/components/progress-bar/ProgressBar.vue",
+ "src/composables/useIntersectionObserver.ts",
+ "src/composables/useComputedDisabled.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/combobox/Combobox.vue"
+ },
+ "src/components/dialog/Dialog.css": {
+ "file": "CdxDialog.css",
+ "src": "src/components/dialog/Dialog.css"
+ },
+ "src/components/dialog/Dialog.vue": {
+ "css": [
+ "CdxDialog.css"
+ ],
+ "file": "CdxDialog.js",
+ "imports": [
+ "src/components/button/Button.vue",
+ "_Icon.js",
+ "src/composables/useGeneratedId.ts",
+ "src/composables/useResizeObserver.ts",
+ "__plugin-vue_export-helper.js",
+ "_constants.js",
+ "_useIconOnlyButton.js",
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/dialog/Dialog.vue"
+ },
+ "src/components/field/Field.css": {
+ "file": "CdxField.css",
+ "src": "src/components/field/Field.css"
+ },
+ "src/components/field/Field.vue": {
+ "css": [
+ "CdxField.css"
+ ],
+ "file": "CdxField.js",
+ "imports": [
+ "src/components/label/Label.vue",
+ "src/components/message/Message.vue",
+ "_constants.js",
+ "src/composables/useGeneratedId.ts",
+ "src/composables/useComputedDisabled.ts",
+ "__plugin-vue_export-helper.js",
+ "_Icon.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "src/composables/useFieldData.ts",
+ "src/composables/useSplitAttributes.ts",
+ "src/components/button/Button.vue",
+ "_useIconOnlyButton.js",
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/field/Field.vue"
+ },
+ "src/components/icon/Icon.vue": {
+ "file": "CdxIcon.js",
+ "imports": [
+ "_Icon.js",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "_constants.js"
+ ],
+ "isEntry": true,
+ "src": "src/components/icon/Icon.vue"
+ },
+ "src/components/info-chip/InfoChip.css": {
+ "file": "CdxInfoChip.css",
+ "src": "src/components/info-chip/InfoChip.css"
+ },
+ "src/components/info-chip/InfoChip.vue": {
+ "css": [
+ "CdxInfoChip.css"
+ ],
+ "file": "CdxInfoChip.js",
+ "imports": [
+ "_constants.js",
+ "_Icon.js",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/info-chip/InfoChip.vue"
+ },
+ "src/components/label/Label.css": {
+ "file": "CdxLabel.css",
+ "src": "src/components/label/Label.css"
+ },
+ "src/components/label/Label.vue": {
+ "css": [
+ "CdxLabel.css"
+ ],
+ "file": "CdxLabel.js",
+ "imports": [
+ "_Icon.js",
+ "src/composables/useFieldData.ts",
+ "src/composables/useSplitAttributes.ts",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "_constants.js",
+ "src/composables/useComputedDisabled.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/label/Label.vue"
+ },
+ "src/components/lookup/Lookup.css": {
+ "file": "CdxLookup.css",
+ "src": "src/components/lookup/Lookup.css"
+ },
+ "src/components/lookup/Lookup.vue": {
+ "css": [
+ "CdxLookup.css"
+ ],
+ "file": "CdxLookup.js",
+ "imports": [
+ "src/components/menu/Menu.vue",
+ "src/components/text-input/TextInput.vue",
+ "src/composables/useGeneratedId.ts",
+ "src/composables/useModelWrapper.ts",
+ "src/composables/useSplitAttributes.ts",
+ "src/composables/useFieldData.ts",
+ "src/composables/useFloatingMenu.ts",
+ "_constants.js",
+ "__plugin-vue_export-helper.js",
+ "src/components/menu-item/MenuItem.vue",
+ "_Icon.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "src/components/thumbnail/Thumbnail.vue",
+ "src/components/search-result-title/SearchResultTitle.vue",
+ "src/components/progress-bar/ProgressBar.vue",
+ "src/composables/useWarnOnce.ts",
+ "src/composables/useIntersectionObserver.ts",
+ "src/composables/useComputedDisabled.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/lookup/Lookup.vue"
+ },
+ "src/components/menu-item/MenuItem.css": {
+ "file": "CdxMenuItem.css",
+ "src": "src/components/menu-item/MenuItem.css"
+ },
+ "src/components/menu-item/MenuItem.vue": {
+ "css": [
+ "CdxMenuItem.css"
+ ],
+ "file": "CdxMenuItem.js",
+ "imports": [
+ "_Icon.js",
+ "src/components/thumbnail/Thumbnail.vue",
+ "src/components/search-result-title/SearchResultTitle.vue",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "_constants.js"
+ ],
+ "isEntry": true,
+ "src": "src/components/menu-item/MenuItem.vue"
+ },
+ "src/components/menu/Menu.css": {
+ "file": "CdxMenu.css",
+ "src": "src/components/menu/Menu.css"
+ },
+ "src/components/menu/Menu.vue": {
+ "css": [
+ "CdxMenu.css"
+ ],
+ "file": "CdxMenu.js",
+ "imports": [
+ "src/components/menu-item/MenuItem.vue",
+ "src/components/progress-bar/ProgressBar.vue",
+ "src/composables/useGeneratedId.ts",
+ "src/composables/useIntersectionObserver.ts",
+ "src/composables/useSplitAttributes.ts",
+ "__plugin-vue_export-helper.js",
+ "_Icon.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "_constants.js",
+ "src/components/thumbnail/Thumbnail.vue",
+ "src/components/search-result-title/SearchResultTitle.vue",
+ "src/composables/useWarnOnce.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/menu/Menu.vue"
+ },
+ "src/components/message/Message.css": {
+ "file": "CdxMessage.css",
+ "src": "src/components/message/Message.css"
+ },
+ "src/components/message/Message.vue": {
+ "css": [
+ "CdxMessage.css"
+ ],
+ "file": "CdxMessage.js",
+ "imports": [
+ "_Icon.js",
+ "src/components/button/Button.vue",
+ "_constants.js",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "_useIconOnlyButton.js",
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/message/Message.vue"
+ },
+ "src/components/progress-bar/ProgressBar.css": {
+ "file": "CdxProgressBar.css",
+ "src": "src/components/progress-bar/ProgressBar.css"
+ },
+ "src/components/progress-bar/ProgressBar.vue": {
+ "css": [
+ "CdxProgressBar.css"
+ ],
+ "file": "CdxProgressBar.js",
+ "imports": [
+ "src/composables/useWarnOnce.ts",
+ "__plugin-vue_export-helper.js"
+ ],
+ "isEntry": true,
+ "src": "src/components/progress-bar/ProgressBar.vue"
+ },
+ "src/components/radio/Radio.css": {
+ "file": "CdxRadio.css",
+ "src": "src/components/radio/Radio.css"
+ },
+ "src/components/radio/Radio.vue": {
+ "css": [
+ "CdxRadio.css"
+ ],
+ "file": "CdxRadio.js",
+ "imports": [
+ "src/components/label/Label.vue",
+ "_useLabelChecker.js",
+ "src/composables/useModelWrapper.ts",
+ "src/composables/useGeneratedId.ts",
+ "src/composables/useFieldData.ts",
+ "__plugin-vue_export-helper.js",
+ "_Icon.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "_constants.js",
+ "src/composables/useSplitAttributes.ts",
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts",
+ "src/composables/useComputedDisabled.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/radio/Radio.vue"
+ },
+ "src/components/search-input/SearchInput.css": {
+ "file": "CdxSearchInput.css",
+ "src": "src/components/search-input/SearchInput.css"
+ },
+ "src/components/search-input/SearchInput.vue": {
+ "css": [
+ "CdxSearchInput.css"
+ ],
+ "file": "CdxSearchInput.js",
+ "imports": [
+ "_Icon.js",
+ "src/components/button/Button.vue",
+ "src/components/text-input/TextInput.vue",
+ "src/composables/useModelWrapper.ts",
+ "src/composables/useSplitAttributes.ts",
+ "src/composables/useFieldData.ts",
+ "_constants.js",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "_useIconOnlyButton.js",
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts",
+ "src/composables/useComputedDisabled.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/search-input/SearchInput.vue"
+ },
+ "src/components/search-result-title/SearchResultTitle.css": {
+ "file": "CdxSearchResultTitle.css",
+ "src": "src/components/search-result-title/SearchResultTitle.css"
+ },
+ "src/components/search-result-title/SearchResultTitle.vue": {
+ "css": [
+ "CdxSearchResultTitle.css"
+ ],
+ "file": "CdxSearchResultTitle.js",
+ "imports": [
+ "__plugin-vue_export-helper.js"
+ ],
+ "isEntry": true,
+ "src": "src/components/search-result-title/SearchResultTitle.vue"
+ },
+ "src/components/select/Select.css": {
+ "file": "CdxSelect.css",
+ "src": "src/components/select/Select.css"
+ },
+ "src/components/select/Select.vue": {
+ "css": [
+ "CdxSelect.css"
+ ],
+ "file": "CdxSelect.js",
+ "imports": [
+ "_Icon.js",
+ "src/components/menu/Menu.vue",
+ "src/composables/useGeneratedId.ts",
+ "src/composables/useModelWrapper.ts",
+ "src/composables/useFieldData.ts",
+ "src/composables/useSplitAttributes.ts",
+ "src/composables/useFloatingMenu.ts",
+ "_constants.js",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "src/components/menu-item/MenuItem.vue",
+ "src/components/thumbnail/Thumbnail.vue",
+ "src/components/search-result-title/SearchResultTitle.vue",
+ "src/components/progress-bar/ProgressBar.vue",
+ "src/composables/useWarnOnce.ts",
+ "src/composables/useIntersectionObserver.ts",
+ "src/composables/useComputedDisabled.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/select/Select.vue"
+ },
+ "src/components/tab/Tab.css": {
+ "file": "CdxTab.css",
+ "src": "src/components/tab/Tab.css"
+ },
+ "src/components/tab/Tab.vue": {
+ "css": [
+ "CdxTab.css"
+ ],
+ "file": "CdxTab.js",
+ "imports": [
+ "_constants.js",
+ "__plugin-vue_export-helper.js"
+ ],
+ "isEntry": true,
+ "src": "src/components/tab/Tab.vue"
+ },
+ "src/components/tabs/Tabs.css": {
+ "file": "CdxTabs.css",
+ "src": "src/components/tabs/Tabs.css"
+ },
+ "src/components/tabs/Tabs.vue": {
+ "css": [
+ "CdxTabs.css"
+ ],
+ "file": "CdxTabs.js",
+ "imports": [
+ "_Icon.js",
+ "src/components/button/Button.vue",
+ "src/components/tab/Tab.vue",
+ "src/composables/useGeneratedId.ts",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useModelWrapper.ts",
+ "src/composables/useIntersectionObserver.ts",
+ "_useSlotContents2.js",
+ "_constants.js",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedLanguage.ts",
+ "_useIconOnlyButton.js",
+ "src/composables/useWarnOnce.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/tabs/Tabs.vue"
+ },
+ "src/components/text-area/TextArea.css": {
+ "file": "CdxTextArea.css",
+ "src": "src/components/text-area/TextArea.css"
+ },
+ "src/components/text-area/TextArea.vue": {
+ "css": [
+ "CdxTextArea.css"
+ ],
+ "file": "CdxTextArea.js",
+ "imports": [
+ "_Icon.js",
+ "src/composables/useSplitAttributes.ts",
+ "src/composables/useModelWrapper.ts",
+ "src/composables/useFieldData.ts",
+ "_constants.js",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "src/composables/useComputedDisabled.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/text-area/TextArea.vue"
+ },
+ "src/components/text-input/TextInput.css": {
+ "file": "CdxTextInput.css",
+ "src": "src/components/text-input/TextInput.css"
+ },
+ "src/components/text-input/TextInput.vue": {
+ "css": [
+ "CdxTextInput.css"
+ ],
+ "file": "CdxTextInput.js",
+ "imports": [
+ "_Icon.js",
+ "_constants.js",
+ "src/composables/useModelWrapper.ts",
+ "src/composables/useSplitAttributes.ts",
+ "src/composables/useFieldData.ts",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "src/composables/useComputedDisabled.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/text-input/TextInput.vue"
+ },
+ "src/components/thumbnail/Thumbnail.css": {
+ "file": "CdxThumbnail.css",
+ "src": "src/components/thumbnail/Thumbnail.css"
+ },
+ "src/components/thumbnail/Thumbnail.vue": {
+ "css": [
+ "CdxThumbnail.css"
+ ],
+ "file": "CdxThumbnail.js",
+ "imports": [
+ "_Icon.js",
+ "_constants.js",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/thumbnail/Thumbnail.vue"
+ },
+ "src/components/toggle-button-group/ToggleButtonGroup.css": {
+ "file": "CdxToggleButtonGroup.css",
+ "src": "src/components/toggle-button-group/ToggleButtonGroup.css"
+ },
+ "src/components/toggle-button-group/ToggleButtonGroup.vue": {
+ "css": [
+ "CdxToggleButtonGroup.css"
+ ],
+ "file": "CdxToggleButtonGroup.js",
+ "imports": [
+ "_buttonHelpers.js",
+ "_Icon.js",
+ "src/components/toggle-button/ToggleButton.vue",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "_constants.js",
+ "_useIconOnlyButton.js",
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/toggle-button-group/ToggleButtonGroup.vue"
+ },
+ "src/components/toggle-button/ToggleButton.css": {
+ "file": "CdxToggleButton.css",
+ "src": "src/components/toggle-button/ToggleButton.css"
+ },
+ "src/components/toggle-button/ToggleButton.vue": {
+ "css": [
+ "CdxToggleButton.css"
+ ],
+ "file": "CdxToggleButton.js",
+ "imports": [
+ "_useIconOnlyButton.js",
+ "__plugin-vue_export-helper.js",
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/toggle-button/ToggleButton.vue"
+ },
+ "src/components/toggle-switch/ToggleSwitch.css": {
+ "file": "CdxToggleSwitch.css",
+ "src": "src/components/toggle-switch/ToggleSwitch.css"
+ },
+ "src/components/toggle-switch/ToggleSwitch.vue": {
+ "css": [
+ "CdxToggleSwitch.css"
+ ],
+ "file": "CdxToggleSwitch.js",
+ "imports": [
+ "src/components/label/Label.vue",
+ "_useLabelChecker.js",
+ "src/composables/useModelWrapper.ts",
+ "src/composables/useGeneratedId.ts",
+ "src/composables/useSplitAttributes.ts",
+ "src/composables/useFieldData.ts",
+ "__plugin-vue_export-helper.js",
+ "_Icon.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "_constants.js",
+ "_useSlotContents2.js",
+ "src/composables/useWarnOnce.ts",
+ "src/composables/useComputedDisabled.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/toggle-switch/ToggleSwitch.vue"
+ },
+ "src/components/typeahead-search/TypeaheadSearch.css": {
+ "file": "CdxTypeaheadSearch.css",
+ "src": "src/components/typeahead-search/TypeaheadSearch.css"
+ },
+ "src/components/typeahead-search/TypeaheadSearch.vue": {
+ "css": [
+ "CdxTypeaheadSearch.css"
+ ],
+ "file": "CdxTypeaheadSearch.js",
+ "imports": [
+ "_Icon.js",
+ "src/components/menu/Menu.vue",
+ "src/components/search-input/SearchInput.vue",
+ "src/composables/useGeneratedId.ts",
+ "src/composables/useSplitAttributes.ts",
+ "_constants.js",
+ "__plugin-vue_export-helper.js",
+ "src/composables/useComputedDirection.ts",
+ "src/composables/useComputedLanguage.ts",
+ "src/components/menu-item/MenuItem.vue",
+ "src/components/thumbnail/Thumbnail.vue",
+ "src/components/search-result-title/SearchResultTitle.vue",
+ "src/components/progress-bar/ProgressBar.vue",
+ "src/composables/useWarnOnce.ts",
+ "src/composables/useIntersectionObserver.ts",
+ "src/components/button/Button.vue",
+ "_useIconOnlyButton.js",
+ "_useSlotContents2.js",
+ "src/components/text-input/TextInput.vue",
+ "src/composables/useModelWrapper.ts",
+ "src/composables/useFieldData.ts",
+ "src/composables/useComputedDisabled.ts"
+ ],
+ "isEntry": true,
+ "src": "src/components/typeahead-search/TypeaheadSearch.vue"
+ },
+ "src/composables/useComputedDirection.ts": {
+ "file": "useComputedDirection.js",
+ "isEntry": true,
+ "src": "src/composables/useComputedDirection.ts"
+ },
+ "src/composables/useComputedDisabled.ts": {
+ "file": "useComputedDisabled.js",
+ "imports": [
+ "_constants.js"
+ ],
+ "isEntry": true,
+ "src": "src/composables/useComputedDisabled.ts"
+ },
+ "src/composables/useComputedLanguage.ts": {
+ "file": "useComputedLanguage.js",
+ "isEntry": true,
+ "src": "src/composables/useComputedLanguage.ts"
+ },
+ "src/composables/useFieldData.ts": {
+ "file": "useFieldData.js",
+ "imports": [
+ "src/composables/useComputedDisabled.ts",
+ "_constants.js"
+ ],
+ "isEntry": true,
+ "src": "src/composables/useFieldData.ts"
+ },
+ "src/composables/useFloatingMenu.ts": {
+ "file": "useFloatingMenu.js",
+ "isEntry": true,
+ "src": "src/composables/useFloatingMenu.ts"
+ },
+ "src/composables/useGeneratedId.ts": {
+ "file": "useGeneratedId.js",
+ "imports": [
+ "_constants.js"
+ ],
+ "isEntry": true,
+ "src": "src/composables/useGeneratedId.ts"
+ },
+ "src/composables/useIntersectionObserver.ts": {
+ "file": "useIntersectionObserver.js",
+ "isEntry": true,
+ "src": "src/composables/useIntersectionObserver.ts"
+ },
+ "src/composables/useModelWrapper.ts": {
+ "file": "useModelWrapper.js",
+ "isEntry": true,
+ "src": "src/composables/useModelWrapper.ts"
+ },
+ "src/composables/useResizeObserver.ts": {
+ "file": "useResizeObserver.js",
+ "isEntry": true,
+ "src": "src/composables/useResizeObserver.ts"
+ },
+ "src/composables/useSlotContents.ts": {
+ "file": "useSlotContents.js",
+ "imports": [
+ "_useSlotContents2.js"
+ ],
+ "isEntry": true,
+ "src": "src/composables/useSlotContents.ts"
+ },
+ "src/composables/useSplitAttributes.ts": {
+ "file": "useSplitAttributes.js",
+ "isEntry": true,
+ "src": "src/composables/useSplitAttributes.ts"
+ },
+ "src/composables/useWarnOnce.ts": {
+ "file": "useWarnOnce.js",
+ "isEntry": true,
+ "src": "src/composables/useWarnOnce.ts"
+ }
+} \ No newline at end of file
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useComputedDirection.js b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useComputedDirection.js
new file mode 100644
index 000000000000..3ba3b43d2d0d
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useComputedDirection.js
@@ -0,0 +1 @@
+"use strict";const u=require("vue");function o(n){const t=u.ref(null);return u.onMounted(()=>{const e=window.getComputedStyle(n.value).direction;t.value=e==="ltr"||e==="rtl"?e:null}),t}module.exports=o;
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useComputedLanguage.js b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useComputedLanguage.js
new file mode 100644
index 000000000000..95a40103c34c
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useComputedLanguage.js
@@ -0,0 +1 @@
+"use strict";const t=require("vue");function a(u){const n=t.ref("");return t.onMounted(()=>{let e=u.value;for(;e&&e.lang==="";)e=e.parentElement;n.value=e?e.lang:null}),n}module.exports=a;
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useIconOnlyButton.js b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useIconOnlyButton.js
new file mode 100644
index 000000000000..d03b042e4ca0
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useIconOnlyButton.js
@@ -0,0 +1 @@
+"use strict";const i=require("vue"),n=require("./useSlotContents.js"),c=require("./useWarnOnce.js");function l(u,t,r){const o=i.computed(()=>{const s=n.useSlotContents(u);if(s.length!==1)return!1;const e=s[0];return!!(typeof e=="object"&&(n.isComponentVNode(e,"CdxIcon")||n.isTagVNode(e,"svg")))});return c.useWarnOnce(()=>o.value&&!t["aria-label"]&&!t["aria-hidden"],"".concat(r,": Icon-only buttons require one of the following attributes: aria-label or aria-hidden. See documentation at https://doc.wikimedia.org/codex/latest/components/demos/button.html#icon-only-button")),o}exports.useIconOnlyButton=l;
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useModelWrapper.js b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useModelWrapper.js
new file mode 100644
index 000000000000..8a698b8dd1c5
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useModelWrapper.js
@@ -0,0 +1 @@
+"use strict";const o=require("vue");function p(e,u,r){return o.computed({get:()=>e.value,set:t=>u(r||"update:modelValue",t)})}exports.useModelWrapper=p;
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useSlotContents.js b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useSlotContents.js
new file mode 100644
index 000000000000..4b2d2408fe11
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useSlotContents.js
@@ -0,0 +1 @@
+"use strict";const o=require("vue");function r(e){const t=[];for(const n of e)typeof n.type=="string"||typeof n.type=="object"?t.push(n):n.type!==o.Comment&&(typeof n.children=="string"&&n.children.trim()!==""?t.push(n.children):Array.isArray(n.children)&&t.push(...r(n.children)));return t}function i(e,t){return typeof e.type=="object"&&"name"in e.type?t!==void 0?e.type.name===t:!0:!1}function s(e,t){return typeof e.type=="string"?t!==void 0?e.type===t.toLowerCase():!0:!1}function f(e){const t=typeof e=="function"?e():e;return t?r(t):[]}exports.isComponentVNode=i;exports.isTagVNode=s;exports.useSlotContents=f;
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useSlotContents2.js b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useSlotContents2.js
new file mode 100644
index 000000000000..4b2d2408fe11
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useSlotContents2.js
@@ -0,0 +1 @@
+"use strict";const o=require("vue");function r(e){const t=[];for(const n of e)typeof n.type=="string"||typeof n.type=="object"?t.push(n):n.type!==o.Comment&&(typeof n.children=="string"&&n.children.trim()!==""?t.push(n.children):Array.isArray(n.children)&&t.push(...r(n.children)));return t}function i(e,t){return typeof e.type=="object"&&"name"in e.type?t!==void 0?e.type.name===t:!0:!1}function s(e,t){return typeof e.type=="string"?t!==void 0?e.type===t.toLowerCase():!0:!1}function f(e){const t=typeof e=="function"?e():e;return t?r(t):[]}exports.isComponentVNode=i;exports.isTagVNode=s;exports.useSlotContents=f;
diff --git a/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useWarnOnce.js b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useWarnOnce.js
new file mode 100644
index 000000000000..3fd4aab04768
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex-devmode/packages/codex/dist/modules/useWarnOnce.js
@@ -0,0 +1 @@
+"use strict";const n=require("vue");function u(r,t){if(r()){n.warn(t);return}const c=n.watch(r,e=>{e&&(n.warn(t),c())})}exports.useWarnOnce=u;
diff --git a/tests/phpunit/data/resourceloader/codex/codex.style.css b/tests/phpunit/data/resourceloader/codex/codex.style.css
new file mode 100644
index 000000000000..8718496a546a
--- /dev/null
+++ b/tests/phpunit/data/resourceloader/codex/codex.style.css
@@ -0,0 +1 @@
+/* Placeholder file for tests */ \ No newline at end of file
diff --git a/tests/phpunit/includes/ResourceLoader/CodexModuleTest.php b/tests/phpunit/includes/ResourceLoader/CodexModuleTest.php
index 5001128a6547..8d0d291ab660 100644
--- a/tests/phpunit/includes/ResourceLoader/CodexModuleTest.php
+++ b/tests/phpunit/includes/ResourceLoader/CodexModuleTest.php
@@ -3,7 +3,9 @@
namespace MediaWiki\Tests\ResourceLoader;
use InvalidArgumentException;
+use MediaWiki\MainConfigNames;
use MediaWiki\ResourceLoader\CodexModule;
+use RuntimeException;
use Wikimedia\TestingAccessWrapper;
/**
@@ -12,7 +14,8 @@ use Wikimedia\TestingAccessWrapper;
*/
class CodexModuleTest extends ResourceLoaderTestCase {
- public const FIXTURE_PATH = 'tests/phpunit/data/resourceloader/codex/';
+ public const FIXTURE_PATH = 'tests/phpunit/data/resourceloader/codex';
+ public const DEVMODE_FIXTURE_PATH = 'tests/phpunit/data/resourceloader/codex-devmode';
public static function provideModuleConfig() {
yield 'Codex subset' => [
@@ -187,7 +190,7 @@ class CodexModuleTest extends ResourceLoaderTestCase {
}
$testModule = new class( $moduleDefinition ) extends CodexModule {
- public const CODEX_LIBRARY_DIR = CodexModuleTest::FIXTURE_PATH;
+ public const CODEX_DEFAULT_LIBRARY_DIR = CodexModuleTest::FIXTURE_PATH;
};
$context = $this->getResourceLoaderContext();
@@ -205,7 +208,7 @@ class CodexModuleTest extends ResourceLoaderTestCase {
$styleFilenames = [];
if ( count( $styleFiles ) > 0 ) {
$styleFilenames = array_map( static function ( $filepath ) use ( $testModule ) {
- return str_replace( $testModule::CODEX_LIBRARY_DIR, '', $filepath->getPath() );
+ return str_replace( $testModule::CODEX_DEFAULT_LIBRARY_DIR . '/', '', $filepath->getPath() );
}, $styleFiles[ 'all' ] );
}
$this->assertEquals( $expected[ 'styles' ] ?? [], $styleFilenames, 'Correct styleFiles added' );
@@ -217,7 +220,7 @@ class CodexModuleTest extends ResourceLoaderTestCase {
];
$testModule = new class( $moduleDefinition ) extends CodexModule {
- public const CODEX_LIBRARY_DIR = CodexModuleTest::FIXTURE_PATH;
+ public const CODEX_DEFAULT_LIBRARY_DIR = CodexModuleTest::FIXTURE_PATH;
};
$context = $this->getResourceLoaderContext();
@@ -240,19 +243,170 @@ class CodexModuleTest extends ResourceLoaderTestCase {
public function testGetManifestFile() {
$moduleDefinition = [ 'codexComponents' => [ 'CdxButton', 'CdxMessage' ] ];
$testModule = new class( $moduleDefinition ) extends CodexModule {
- public const CODEX_LIBRARY_DIR = CodexModuleTest::FIXTURE_PATH;
+ public const CODEX_DEFAULT_LIBRARY_DIR = CodexModuleTest::FIXTURE_PATH;
};
$context = $this->getResourceLoaderContext();
+ $config = $context->getResourceLoader()->getConfig();
+ $testModule->setConfig( $config );
$testWrapper = TestingAccessWrapper::newFromObject( $testModule );
// By default, look for a manifest file called "manifest.json"
$this->assertEquals(
- MW_INSTALL_PATH . '/' . self::FIXTURE_PATH . 'modules/manifest.json',
+ MW_INSTALL_PATH . '/' . self::FIXTURE_PATH . '/modules/manifest.json',
$testWrapper->getManifestFilePath( $context )
);
}
+ public function testGetMessages() {
+ $messageKeysFromFile = json_decode( file_get_contents(
+ MW_INSTALL_PATH . '/' . self::FIXTURE_PATH . '/messageKeys.json'
+ ) );
+
+ $moduleDefinition = [ 'codexComponents' => [ 'CdxButton', 'CdxMessage' ] ];
+ $testModule = new class( $moduleDefinition ) extends CodexModule {
+ public const CODEX_DEFAULT_LIBRARY_DIR = CodexModuleTest::FIXTURE_PATH;
+ };
+ $context = $this->getResourceLoaderContext();
+ $config = $context->getResourceLoader()->getConfig();
+ $testModule->setConfig( $config );
+ $this->assertEquals(
+ $messageKeysFromFile,
+ $testModule->getMessages(),
+ 'i18n messages from messageKeys.json are added'
+ );
+
+ $moduleDefinition = [
+ 'codexComponents' => [ 'CdxButton', 'CdxMessage' ],
+ 'messages' => [ 'monday', 'tuesday' ]
+ ];
+ $testModule = new class( $moduleDefinition ) extends CodexModule {
+ public const CODEX_DEFAULT_LIBRARY_DIR = CodexModuleTest::FIXTURE_PATH;
+ };
+ $context = $this->getResourceLoaderContext();
+ $config = $context->getResourceLoader()->getConfig();
+ $testModule->setConfig( $config );
+ $this->assertEquals(
+ array_merge( [ 'monday', 'tuesday' ], $messageKeysFromFile ),
+ $testModule->getMessages(),
+ 'i18n messages from messageKeys.json are in addition to messages in module definition'
+ );
+
+ $moduleDefinition = [
+ 'codexFullLibrary' => true
+ ];
+ $testModule = new class( $moduleDefinition ) extends CodexModule {
+ public const CODEX_DEFAULT_LIBRARY_DIR = CodexModuleTest::FIXTURE_PATH;
+ };
+ $context = $this->getResourceLoaderContext();
+ $config = $context->getResourceLoader()->getConfig();
+ $testModule->setConfig( $config );
+ $this->assertEquals(
+ $messageKeysFromFile,
+ $testModule->getMessages(),
+ 'i18n messages are added for full library modules'
+ );
+
+ $moduleDefinition = [
+ 'codexComponents' => [ 'CdxButton', 'CdxMessage' ],
+ 'codexStyleOnly' => true
+ ];
+ $testModule = new class( $moduleDefinition ) extends CodexModule {
+ public const CODEX_DEFAULT_LIBRARY_DIR = CodexModuleTest::FIXTURE_PATH;
+ };
+ $context = $this->getResourceLoaderContext();
+ $config = $context->getResourceLoader()->getConfig();
+ $testModule->setConfig( $config );
+ $this->assertEquals(
+ [],
+ $testModule->getMessages(),
+ 'i18n messages are not added for style-only modules'
+ );
+ }
+
+ public function testDevMode() {
+ $devDir = MW_INSTALL_PATH . '/' . self::DEVMODE_FIXTURE_PATH;
+ $this->overrideConfigValues( [
+ MainConfigNames::CodexDevelopmentDir => $devDir
+ ] );
+
+ $moduleDefinition = [ 'codexComponents' => [ 'CdxButton', 'CdxMessage' ] ];
+ $testModule = new class( $moduleDefinition ) extends CodexModule {
+ public const CODEX_DEFAULT_LIBRARY_DIR = CodexModuleTest::FIXTURE_PATH;
+ };
+
+ $context = $this->getResourceLoaderContext();
+ $config = $context->getResourceLoader()->getConfig();
+ $testModule->setConfig( $config );
+ $testWrapper = TestingAccessWrapper::newFromObject( $testModule );
+
+ $this->assertEquals(
+ $devDir . '/packages/codex/dist/modules/manifest.json',
+ $testWrapper->getManifestFilePath( $context ),
+ 'Manifest path is based on dev mode path'
+ );
+
+ $packageFiles = $testModule->getPackageFiles( $context );
+ $this->assertEquals(
+ $devDir . '/packages/codex/dist/modules/CdxButton.js',
+ $packageFiles[ 'files' ][ '_codex/CdxButton.js' ][ 'filePath' ]->getLocalPath(),
+ 'Package file paths are based on dev mode path'
+ );
+
+ $styleFiles = $testModule->getStyleFiles( $context );
+ $this->assertEquals(
+ $devDir . '/packages/codex/dist/modules/CdxButton.css',
+ $styleFiles[ 'all' ][ 0 ]->getLocalPath(),
+ 'Style file paths are based on dev mode path'
+ );
+
+ $this->assertEquals(
+ [ 'cdx-test-message-1', 'cdx-test-message-2' ],
+ $testModule->getMessages(),
+ 'i18n message keys come from messages file in dev mode path'
+ );
+
+ $fullLibraryModuleDefinition = [ 'codexFullLibrary' => true ];
+ $fullLibraryModule = new class( $fullLibraryModuleDefinition ) extends CodexModule {
+ public const CODEX_DEFAULT_LIBRARY_DIR = CodexModuleTest::FIXTURE_PATH;
+ };
+ $fullLibraryModule->setConfig( $config );
+
+ $packageFiles = $fullLibraryModule->getPackageFiles( $context );
+ $this->assertEquals(
+ $devDir . '/packages/codex/dist/codex.umd.cjs',
+ $packageFiles[ 'files' ][ 'codex.js' ][ 'filePath' ]->getLocalPath(),
+ 'Full library module script path is based on dev mode path'
+ );
+
+ $styleFiles = $fullLibraryModule->getStyleFiles( $context );
+ $this->assertEquals(
+ $devDir . '/packages/codex/dist/codex.style.css',
+ $styleFiles[ 'all' ][ 0 ]->getLocalPath(),
+ 'Full library module style path is based on dev mode path'
+ );
+ }
+
+ public function testDevModeException() {
+ $badDir = MW_INSTALL_PATH . '/' . self::DEVMODE_FIXTURE_PATH . '/path/that/does/not/exist';
+ $this->overrideConfigValues( [
+ MainConfigNames::CodexDevelopmentDir => $badDir
+ ] );
+
+ $this->expectException( RuntimeException::class );
+ $this->expectExceptionMessage( 'Could not find Codex development build' );
+
+ $moduleDefinition = [ 'codexComponents' => [ 'CdxButton', 'CdxMessage' ] ];
+ $testModule = new class( $moduleDefinition ) extends CodexModule {
+ public const CODEX_DEFAULT_LIBRARY_DIR = CodexModuleTest::FIXTURE_PATH;
+ };
+ $context = $this->getResourceLoaderContext();
+ $config = $context->getResourceLoader()->getConfig();
+ $testModule->setConfig( $config );
+
+ $testModule->getPackageFiles( $context );
+ }
+
/**
* Test that the manifest data structure is transformed correctly.
* This test relies on the fixture manifest data that lives in
@@ -261,10 +415,12 @@ class CodexModuleTest extends ResourceLoaderTestCase {
public function testGetCodexFiles() {
$moduleDefinition = [ 'codexComponents' => [ 'CdxButton', 'CdxMessage' ] ];
$testModule = new class( $moduleDefinition ) extends CodexModule {
- public const CODEX_LIBRARY_DIR = CodexModuleTest::FIXTURE_PATH;
+ public const CODEX_DEFAULT_LIBRARY_DIR = CodexModuleTest::FIXTURE_PATH;
};
$context = $this->getResourceLoaderContext();
+ $config = $context->getResourceLoader()->getConfig();
+ $testModule->setConfig( $config );
$testWrapper = TestingAccessWrapper::newFromObject( $testModule );
$codexFiles = $testWrapper->getCodexFiles( $context );
@@ -284,4 +440,31 @@ class CodexModuleTest extends ResourceLoaderTestCase {
$this->assertArrayHasKey( 'styles', $codexFiles[ 'files' ][ 'CdxButton.js' ] );
$this->assertArrayHasKey( 'dependencies', $codexFiles[ 'files' ][ 'CdxButton.js' ] );
}
+
+ public function testGetIcons() {
+ $context = $this->getResourceLoaderContext();
+ $config = $context->getResourceLoader()->getConfig();
+
+ $icons = CodexModule::getIcons( $context, $config, [ 'cdxIconAdd', 'cdxIconNext' ] );
+ $this->assertArrayHasKey( 'cdxIconAdd', $icons );
+ $this->assertArrayHasKey( 'cdxIconNext', $icons );
+ $this->assertArrayNotHasKey( 'cdxIconPrevious', $icons );
+ }
+
+ public function testGetIconsInDevMode() {
+ $devDir = MW_INSTALL_PATH . '/' . self::DEVMODE_FIXTURE_PATH;
+ $this->overrideConfigValues( [
+ MainConfigNames::CodexDevelopmentDir => $devDir
+ ] );
+
+ $context = $this->getResourceLoaderContext();
+ $config = $context->getResourceLoader()->getConfig();
+ $icons = CodexModule::getIcons( $context, $config, [ 'cdxIconAdd', 'cdxIconNext' ] );
+ $this->assertArrayHasKey( 'cdxIconAdd', $icons );
+ $this->assertArrayHasKey( 'cdxIconNext', $icons );
+ $this->assertArrayNotHasKey( 'cdxIconPrevious', $icons );
+
+ $this->assertEquals( 'test add icon', $icons['cdxIconAdd'] );
+ $this->assertEquals( 'test next icon', $icons['cdxIconNext'] );
+ }
}
diff --git a/tests/phpunit/includes/ResourceLoader/FileModuleTest.php b/tests/phpunit/includes/ResourceLoader/FileModuleTest.php
index 610ff0f9a371..a53842292ab6 100644
--- a/tests/phpunit/includes/ResourceLoader/FileModuleTest.php
+++ b/tests/phpunit/includes/ResourceLoader/FileModuleTest.php
@@ -467,6 +467,7 @@ class FileModuleTest extends ResourceLoaderTestCase {
'lessVars' => [ 'foo' => '2px', 'Foo' => '#eeeeee' ]
] );
$module->setName( 'test.less' );
+ $module->setConfig( $context->getResourceLoader()->getConfig() );
$styles = $module->getStyles( $context );
$this->assertStringEqualsFile( $basePath . '/styles.css', $styles['all'] );
}
diff --git a/tests/phpunit/includes/ResourceLoader/LessVarFileModuleTest.php b/tests/phpunit/includes/ResourceLoader/LessVarFileModuleTest.php
index 042b44bb8bdb..d82a78d5ef44 100644
--- a/tests/phpunit/includes/ResourceLoader/LessVarFileModuleTest.php
+++ b/tests/phpunit/includes/ResourceLoader/LessVarFileModuleTest.php
@@ -55,6 +55,7 @@ class LessVarFileModuleTest extends ResourceLoaderTestCase {
'styles' => [ 'less-messages.less' ],
'lessMessages' => [ 'pieday' ],
] );
+ $module->setConfig( $context->getResourceLoader()->getConfig() );
$module->setMessageBlob( '{"pieday":"March 14"}', 'qqx' );
$styles = $module->getStyles( $context );
@@ -69,6 +70,7 @@ class LessVarFileModuleTest extends ResourceLoaderTestCase {
'styles' => [ 'less-messages.less' ],
'lessMessages' => [ 'pieday' ],
] );
+ $module->setConfig( $context->getResourceLoader()->getConfig() );
$module->setMessageBlob( '{"something":"Else"}', 'qqx' );
$styles = $module->getStyles( $context );
diff --git a/tests/phpunit/includes/ResourceLoader/ResourceLoaderTest.php b/tests/phpunit/includes/ResourceLoader/ResourceLoaderTest.php
index 1ca2b19e265d..69b27b1b794d 100644
--- a/tests/phpunit/includes/ResourceLoader/ResourceLoaderTest.php
+++ b/tests/phpunit/includes/ResourceLoader/ResourceLoaderTest.php
@@ -227,10 +227,22 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
'expected' => "$basePath/import-codex-icons.css"
],
[
+ 'input' => "$basePath/import-codex-icons.less",
+ 'expected' => "$basePath/import-codex-icons-devmode.css",
+ 'exception' => null,
+ 'devmode' => true
+ ],
+ [
'input' => "$basePath/import-codex-tokens.less",
'expected' => "$basePath/import-codex-tokens.css"
],
[
+ 'input' => "$basePath/import-codex-tokens.less",
+ 'expected' => "$basePath/import-codex-tokens-devmode.css",
+ 'exception' => null,
+ 'devmode' => true
+ ],
+ [
'input' => "$basePath/import-codex-tokens-npm.less",
'expected' => null,
'exception' => [
@@ -245,8 +257,20 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
/**
* @dataProvider provideLessImportRemappingCases
*/
- public function testLessImportRemapping( $input, $expected, $exception = null ) {
- $rl = new EmptyResourceLoader();
+ public function testLessImportRemapping( $input, $expected, $exception = null, $devmode = false ) {
+ $configOverrides = [];
+ if ( $devmode ) {
+ $devDir = MW_INSTALL_PATH . '/tests/phpunit/data/resourceloader/codex-devmode';
+ $configOverrides += [
+ MainConfigNames::CodexDevelopmentDir => $devDir
+ ];
+ }
+
+ $this->overrideConfigValues( $configOverrides );
+ // Unfortunately the EmptyResourceLoader constructor doesn't pick up the overridden config
+ // values, we have to do that separately
+ $baseConfig = static::getSettings();
+ $rl = new EmptyResourceLoader( new HashConfig( $configOverrides + $baseConfig ) );
$lc = $rl->getLessCompiler();
if ( $exception !== null ) {
@@ -301,6 +325,7 @@ class ResourceLoaderTest extends ResourceLoaderTestCase {
'localBasePath' => __DIR__ . '/../../data/less',
'styles' => [ 'use-variables.less' ],
] );
+ $module->setConfig( $context->getResourceLoader()->getConfig() );
$module->setName( 'test.less' );
$styles = $module->getStyles( $context );
$this->assertStringEqualsFile( $expectedFile, $styles['all'] );