Opened 3 weeks ago

Last modified 3 weeks ago

#35371 new Bug

False positive in JS module aggregation export regex when an export declaration precedes an import declaration

Reported by: Michael Owned by: nobody
Component: contrib.staticfiles Version: dev
Severity: Normal Keywords:
Cc: Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

This regex from django/contrib/staticfiles/storage.py -> HashedFilesMixin -> _js_module_import_aggregation_patterns:

            (
                (
                    r"""(?P<matched>export(?s:(?P<exports>[\s\{].*?))"""
                    r"""\s*from\s*["'](?P<url>[./].*?)["']\s*;)"""
                ),
                """export%(exports)s from "%(url)s";""",
            ),

Can result in a matchobj like this:

> /home/michael/.venv/project/lib/python3.11/site-packages/django/contrib/staticfiles/storage.py(225)converter()

(Pdb) pp matchobj.re
re.compile('(?P<matched>export(?s:(?P<exports>[\\s\\{].*?))\\s*from\\s*["\'](?P<url>[./].*?)["\']\\s*;)', re.IGNORECASE)

(Pdb) pp matchobj.groupdict()
{'exports': ' async function getTakeMessageDialog(a,t={}){const '
            'e=f();e.toggleAttribute("top",!0);const '
            'i=document.createElement("wc-take-message");for(i.toggleAttribute("caneditcontacts",a),t&&(i.props=t),e.appendChild(i),e.save(null,{heading:"Take '
            'a Message",confirm:"Submit"}),i.focus();;){if(!await '
            'e.waitClickedAction())return;if(!i.validate())continue;const '
            'n=await '
            'c.createTakeMessage(i.props);if(n.ok){e.close(),n.notifySuccess("Message '
            'taken");return}}}import w',
 'matched': 'export async function getTakeMessageDialog(a,t={}){const '
            'e=f();e.toggleAttribute("top",!0);const '
            'i=document.createElement("wc-take-message");for(i.toggleAttribute("caneditcontacts",a),t&&(i.props=t),e.appendChild(i),e.save(null,{heading:"Take '
            'a Message",confirm:"Submit"}),i.focus();;){if(!await '
            'e.waitClickedAction())return;if(!i.validate())continue;const '
            'n=await '
            'c.createTakeMessage(i.props);if(n.ok){e.close(),n.notifySuccess("Message '
            'taken");return}}}import w from '
            '"/static/skin/skin/x-field.min.c6fe58e9f403.css";',
 'url': '/static/skin/skin/x-field.min.c6fe58e9f403.css'}

(Pdb) matchobj.string
'var l=Object.defineProperty;var p=(a,t,e)=>t in a?l(a,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):a[t]=e;var o=(a,t,e)=>(p(a,typeof t!="symbol"?t+"":t,e),e);import*as r from"/static/jsapp/jsapp/dtmod.min.js";import*as c from"/static/comms/jsapp/fetcher.min.js";import*as g from"/static/jsapp/jsapp/ui.min.js";import*as m from"/static/jsapp/jsapp/formmod.min.js";import{createDialog as f} from "/static/wcapp/wcapp/wc-dialog.min.2f89ad88aab3.js";import{BaseComponent as h} from "/static/wcapp/jsapp/wc-base.min.425310100bce.js";class d extends h{init(){g.setupAutoHeight(this.get("MESSAGE")),this.initProps(!1)}setupEvents(){this.get("EDIT").onclick=this.setEditMode.bind(this,!0),this.get("CANCEL").onclick=this.setEditMode.bind(this,!1),this.get("SAVE").onclick=async()=>{if(!this.validate())return;(await c.updateTakeMessage(this._props.id,this.props)).ok&&this.setEditMode(!1)}}setEditMode(t){this.toggleAttribute("disabled",!t)}setNoAddContact(){const t=this.get("SEL_ADD_CONTACT").checked;this.toggleAttribute("noaddcontact",!t)}selectContactHandler(t){var e;((e=t.target)==null?void 0:e.getAttribute("name"))==="select_contact"&&this.setNoAddContact()}attributeChangedCallback(t,e,i){switch(t){case"disabled":this.setDisabled(i!==null);break}}set props(t){console.log(t),this._props=t;for(const e of Object.keys(t))switch(e){case"message":this.get("MESSAGE").value=t[e];break;case"createdUtc":this.get("TAKEN_AT").innerHTML=r.formatIsoStr(t[e],r.SHORT_DATETIME);break;case"userName":this.get("TAKEN_BY").innerHTML=t[e];break;case"recipientName":this.get("TAKEN_FOR").innerHTML=t[e];break;case"clientName":const i=t[e];t.client||(this.get("CLIENT_NAME").value=t[e],this.get("CLIENT_NAME").toggleAttribute("required",!0),this.get("CLIENT_NAME_LABEL").classList.remove("d:n"));break;case"client":const s=t[e];s&&(this.get("BADGE").props=s,this.get("BADGE").classList.remove("d:n"));break;case"contact":this.get("CONTACT").props=t[e]}}get props(){var i;return{client:(i=this._props.client)==null?void 0:i.id,clientName:this.get("CLIENT_NAME").value,message:this.get("MESSAGE").value,contact:this.get("CONTACT").props}}setDisabled(t){this.get("CONTACT").disabled=t,this.get("MESSAGE").disabled=t,this.get("CLIENT_NAME").disabled=t}validate(){const t=["CONTACT","MESSAGE"].map(e=>this.get(e));return this.get("CLIENT_NAME").classList.contains("d:n")||t.push(this.get("CLIENT_NAME")),m.manualClientValidation(t)}}o(d,"observedAttributes",["disabled"]),o(d,"properties",["props"]);export async function getTakeMessageDialog(a,t={}){const e=f();e.toggleAttribute("top",!0);const i=document.createElement("wc-take-message");for(i.toggleAttribute("caneditcontacts",a),t&&(i.props=t),e.appendChild(i),e.save(null,{heading:"Take a Message",confirm:"Submit"}),i.focus();;){if(!await e.waitClickedAction())return;if(!i.validate())continue;const n=await c.createTakeMessage(i.props);if(n.ok){e.close(),n.notifySuccess("Message taken");return}}}import w from "/static/skin/skin/x-field.min.c6fe58e9f403.css";import u from "/static/common/skin/cfm-heading.min.b8e21816b92c.css";const b=[w,u];d.initComponent(String.raw`<div id="MAIN" class="d:f f-d:c g:--gap"><div id="TAKEN_TABLE" class="d:g (w>768)g-t-c:repeat(6,auto) (w<768)g-t-c:auto_auto w:f-c g:8"><div x-field="label">By</div><div id="TAKEN_BY" class="m-r:16"></div><div x-field="label">At</div><div id="TAKEN_AT" class="m-r:16"></div><div x-field="label">For</div><div id="TAKEN_FOR"></div></div><div cfm-heading>Client</div><label id="CLIENT_NAME_LABEL" for="CLIENT_NAME" class="d:n d:f? a-i:c f-w:w g:8px_16px"><div x-field="label">Client Name</div><input id="CLIENT_NAME" x-field="widget tonal stadium" type="text" disabled></label><wc-badge id="BADGE" class="d:n w:256 max-w:100%"></wc-badge><div cfm-heading>Contact</div><wc-contact disabled id="CONTACT"></wc-contact><div class="d:f g:--gap f-w:w"><div class="d:f f-d:c g:--gap f-g:15"><label cfm-heading for="MESSAGE">Message</label><textarea id="MESSAGE" rows="4" x-field="widget tonal stadium" class="f-g:1" required disable-if-disabled></textarea></div></div><div class="f-s:i c:--red" show-if-enabled>Please note:<br>The recipient will not be notified or emailed again, and any memo\'s or contacts that were originally created from the message will remain as is.</div><div class="d:f? m-t:16 j-c:e a-i:c f-w:w g:16px_16px" show-if-canedit><wc-button id="EDIT" type="tonal" color="primary" show-if-disabled>EDIT</wc-button><wc-button id="CANCEL" type="tonal"  color="bw" class="min-w:92" show-if-enabled>CANCEL</wc-button><wc-button id="SAVE" type="solid" color="secondary"   class="min-w:92" show-if-enabled>SAVE</wc-button></div></div>\n`,String.raw`:host{display:block}\n*,*:before,*:after{box-sizing:border-box}\n::selection{color:var(--selection-fg, white);background:var(--selection-bg, #888)}\nsvg{vertical-align:bottom}\nsvg,img[src$=".svg"]{flex-shrink:0}\ntextarea{font-family:inherit}\n.a-i\\:c{align-items:center!important}\n.c\\:--red{color:var(--red)!important}\n.d\\:f{display:flex!important}\n.d\\:f\\?{display:flex}\n.d\\:g{display:grid!important}\n.d\\:n{display:none!important}\n.f-d\\:c{flex-direction:column!important}\n.f-g\\:1{flex-grow:1!important}\n.f-g\\:15{flex-grow:15!important}\n.f-s\\:i{font-style:italic!important}\n.f-w\\:w{flex-wrap:wrap!important}\n.g\\:--gap{gap:var(--gap)!important}\n.g\\:16px_16px{gap:16px 16px!important}\n.g\\:8{gap:8px!important}\n.g\\:8px_16px{gap:8px 16px!important}\n.j-c\\:e{justify-content:end!important}\n.m-r\\:16{margin-right:16px!important}\n.m-t\\:16{margin-top:16px!important}\n.max-w\\:100\\%{max-width:100%!important}\n.min-w\\:92{min-width:92px!important}\n.w\\:256{width:256px!important}\n.w\\:f-c{width:fit-content!important}\n@media (max-width:767.999px){.\\(w\\<768\\)g-t-c\\:auto_auto{grid-template-columns:auto auto!important}}\n@media (min-width:768.001px){.\\(w\\>768\\)g-t-c\\:repeat\\(6\\,auto\\){grid-template-columns:repeat(6,auto)!important}}\n:host{display:grid;grid-template-columns:minmax(100%,1024px)}\n:host(:not([disabled])) [show-if-disabled]{display:none!important}\n:host([disabled]) [show-if-enabled]{display:none!important}\n:host(:not([canedit])) [show-if-canedit]{display:none!important}\n`,b,"wc-message-taken",["wc-button","wc-select-client","wc-contact","wc-modal"]);\n'

From this Javascript file:

var r=Object.defineProperty;var p=(n,t,e)=>t in n?r(n,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):n[t]=e;var c=(n,t,e)=>(p(n,typeof t!="symbol"?t+"":t,e),e);import*as d from"/static/comms/jsapp/fetcher.min.js";import*as h from"/static/jsapp/jsapp/ui.min.js";import*as g from"/static/jsapp/jsapp/formmod.min.js";import{createDialog as f}from"/static/wcapp/wcapp/wc-dialog.min.js";import{BaseComponent as u}from"/static/wcapp/jsapp/wc-base.min.js";class l extends u{init(){this._setupPromise=this.setupContext(),h.setupAutoHeight(this.get("MESSAGE")),this.initProps(!1)}setupEvents(){this.get("SELECT_CLIENT").addEventListener("clientChanged",this.changeClientHandler.bind(this)),this.get("SELECT_CLIENT").addEventListener("clientCleared",this.clientClearedHandler.bind(this)),this.get("CONTACTS").addEventListener("click",this.clickButtonHandler.bind(this)),this.get("CONTACTS").addEventListener("change",this.selectContactHandler.bind(this))}setNoAddContact(){const t=this.get("SEL_ADD_CONTACT").checked;this.toggleAttribute("noaddcontact",!t)}selectContactHandler(t){var e;((e=t.target)==null?void 0:e.getAttribute("name"))==="select_contact"&&this.setNoAddContact()}attributeChangedCallback(t,e,i){switch(t){case"disabled":this.setDisabled(i!==null);break}}set props(t){this._props=t}get props(){const t=this.getAttribute("clientid");return{...this._props,client:t?parseInt(t):null,clientName:t?null:this.get("SELECT_CLIENT").pattern,recipient:parseInt(this.get("RECIPIENT").value),message:this.get("MESSAGE").value,contact:this.querySelectedContactEl().props,createMemo:this.get("CREATE_MEMO").checked,addContact:this.get("ADD_CONTACT").checked}}async setupContext(){this._context||(this._context=await this.getContext(),this.buildEmployees(this._context.employees),this.buildClients(this._context.clients))}async buildEmployees(t){if(this.get("RECIPIENT").firstElementChild)return;const e=t.map(s=>{const a=document.createElement("option");return a.value=s.pk,a.innerText=s.fullName,a});this.get("RECIPIENT").replaceChildren(...e);const i=Math.min(e.length,10);!this.hasAttribute("disabled")&&!this.hasAttribute("update")&&this.get("RECIPIENT").setAttribute("size",i),this.get("RECIPIENT").setAttribute("sizebak",i)}async buildClients(t){this.get("SELECT_CLIENT").props=t}async getContext(){if(this._context)return this._context;const t=await d.getContext();if(t.ok)return this._context=t.json,this._context}setDisabled(t){this.shadowRoot.querySelectorAll("[disable-if-disabled]").forEach(i=>i.toggleAttribute("disabled",t)),this.hasAttribute("update")&&this.get("RECIPIENT").toggleAttribute("disabled",!0);const e=this.get("RECIPIENT");this.get("RECIPIENT").hasAttribute("disabled")?e.removeAttribute("size"):e.setAttribute("size",e.getAttribute("sizebak"))}cloneChild(t,e){const i=this.get("CONTACT_TEMPLATE").content.cloneNode(!0).firstElementChild,s=i.querySelector("wc-contact");s.props=t;const a=i.querySelector('[name="select_contact"]'),o=i.querySelector("label");return a.id=`contact_${t.hash}`,o.setAttribute("for",a.id),s.disabled=!0,i}getContactOptions(){return this._contactOptions}selectContact(t){const e=this.get("CONTACTS").querySelector(`[data-hash="${t}"]`),i=e==null?void 0:e.querySelector('[name="contact"]');i&&(i.checked=!0)}setContact(t){t.hash?this.selectContact(t.hash):(this.get("NEW_CONTACT").props=t,this.get("ADD_CONTACT").checked=!0)}setContacts(t){this._contactOptions=t||[];const e=this._contactOptions.map((i,s)=>this.cloneChild(i,s));this.get("CONTACTS_SLOT").replaceChildren(...e)}setExistingClient(t){t?this.setAttribute("clientid",t):this.removeAttribute("clientid"),t||(this.get("SEL_ADD_CONTACT").checked=!0),t||(this.get("ADD_CONTACT").checked=!1),this.get("MESSAGE").autoHeight()}changeClientHandler(t){const e=t.detail.props;this.setExistingClient(e==null?void 0:e.id);const i=(e==null?void 0:e.hashedContacts)||[];this.setContacts(i)}enableEditContact(t,e){const i=e.querySelector("wc-contact");i.disabled=!t,e.querySelector('[data-button="edit"]').classList.toggle("d:n",t),e.querySelector('[data-button="cancel"]').classList.toggle("d:n",!t),e.querySelector('[data-button="save"]').classList.toggle("d:n",!t)}clickButtonHandler(t){const e=t.target.getAttribute("data-button");if(!e)return;t.stopPropagation();const i=t.target.closest(".js-main");switch(e){case"edit":this.enableEditContact(!0,i);break;case"cancel":this.enableEditContact(!1,i);break;case"save":this.updateContact(i);break}}clientClearedHandler(){this.setExistingClient(!1),this.setContacts([]),this.setNoAddContact()}querySelectedMainEl(){return this.get("CONTACTS").querySelector('[name="select_contact"]:checked').closest(".js-main")}querySelectedContactEl(){return this.querySelectedMainEl().querySelector("wc-contact")}focus(){this.get("SELECT_CLIENT").focus()}validate(){const t=["SELECT_CLIENT","RECIPIENT","MESSAGE"].map(e=>this.get(e));return g.manualClientValidation(t)}async updateContact(t){const e=parseInt(this.getAttribute("clientid")),i=t.querySelector("wc-contact"),a={contact:i.props},o=await d.clientUpdateContact(e,a);o.ok&&(this.enableEditContact(!1,t),i.props={...i.props,hash:o.json.newHash})}}c(l,"observedAttributes",["disabled"]),c(l,"properties",["props"]);export async function getTakeMessageDialog(n,t={}){const e=f();e.toggleAttribute("top",!0);const i=document.createElement("wc-take-message");for(i.toggleAttribute("caneditcontacts",n),t&&(i.props=t),e.appendChild(i),e.save(null,{heading:"Take a Message",confirm:"Submit"}),i.focus();;){if(!await e.waitClickedAction())return;if(!i.validate())continue;const a=await d.createTakeMessage(i.props);if(a.ok){e.close(),setTimeout(()=>a.notifySuccess("Message taken"),100);return}}}import m from"/static/skin/skin/x-field.min.css";import C from"/static/common/skin/cfm-heading.min.css";const b=[m,C];l.initComponent(String.raw`<div id="MAIN" class="d:f f-d:c g:--gap"><wc-select-client id="SELECT_CLIENT" newtab selectable sel1stsearch patternisnonelabel disable-if-disabled></wc-select-client><div cfm-heading>Contact</div><div id="CONTACTS" class="d:f f-d:c"><div class="js-main d:f a-i:c g:8px_16px f-g:1"><input id="SEL_ADD_CONTACT" x-field="widget" type="radio" name="select_contact" value="new" checked show-if-clientid><div class="d:f f-d:c g:8 f-g:1"><label for="SEL_ADD_CONTACT" class="f-w:500" show-if-clientid>New Contact</label><wc-contact id="NEW_CONTACT" class="f-g:1" show-if-addcontact disable-if-disabled></wc-contact><label for="ADD_CONTACT" class="d:f g:8 w:f-c m-y:4" show-if-clientid show-if-addcontact show-if-caneditcontacts><input id="ADD_CONTACT" type="checkbox" x-field="widget" disable-if-disabled><div title="If the client exists, it will be updated with this info">Add this contact to the client?</div></label></div></div><slot id="CONTACTS_SLOT" name="contacts"></slot></div><div class="d:f g:--gap f-w:w"><div class="d:f f-d:c g:--gap f-g:10" hide-if-disabled><div cfm-heading>Notify</div><select id="RECIPIENT" x-field="widget tonal stadium" class="o-y:v max-h:640" disable-if-disabled required></select></div><div class="d:f f-d:c g:--gap f-g:15"><label cfm-heading for="MESSAGE">Message</label><textarea id="MESSAGE" rows="4" x-field="widget tonal stadium" class="f-g:1" required disable-if-disabled></textarea><label for="CREATE_MEMO" class="d:f g:8 (w<640)m-y:8" hide-if-disabled show-if-clientid><input id="CREATE_MEMO" type="checkbox" x-field="widget" disable-if-disabled><div class="js-save-action" title="Also create a Client Memo that corresponds to this message">Also create a Client Memo?</div></label></div></div></div><template id="CONTACT_TEMPLATE"><div class="js-main d:f a-i:c j-c:e c-g:24 r-g:8 f-w:w"><div class="d:f a-i:c g:8px_16px f-g:10000"><input x-field="widget" type="radio" x-field="widget" name="select_contact" disable-if-disabled><label x-field="label" class="f-g:1" hide-if-disabled><wc-contact></wc-contact></label></div><div class="d:f a-i:c f-w:w (w>530)f-d:c g:16px_16px" hide-if-disabled show-if-caneditcontacts><wc-button type="tonal" color="primary"   data-button="edit">EDIT</wc-button><wc-button type="text"  color="bw"        data-button="cancel" class="min-w:92 d:n">CANCEL</wc-button><wc-button type="solid" color="secondary" data-button="save"   class="min-w:92 d:n">SAVE</wc-button></div></div></template>
`,String.raw`styles`,b,"wc-take-message",[]);

Which means collect static fails:

Post-processing 'comms/wcapp/wc-take-message.min.js' failed!

Traceback (most recent call last):
  File "/home/michael/project/src/manage.py", line 57, in <module>
    run()
  File "/home/michael/project/src/manage.py", line 49, in run
    execute_from_command_line(sys.argv)
  File "/home/michael/.venv/project/lib/python3.11/site-packages/django/core/management/__init__.py", line 442, in execute_from_command_line
    utility.execute()
  File "/home/michael/.venv/project/lib/python3.11/site-packages/django/core/management/__init__.py", line 436, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/michael/.venv/project/lib/python3.11/site-packages/django/core/management/base.py", line 413, in run_from_argv
    self.execute(*args, **cmd_options)
  File "/home/michael/.venv/project/lib/python3.11/site-packages/django/core/management/base.py", line 459, in execute
    output = self.handle(*args, **options)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/michael/project/src/core/app/base/management/commands/deploy.py", line 84, in handle
    call_command('collectstatic', '--noinput', '--clear', '--verbosity', '0')
  File "/home/michael/.venv/project/lib/python3.11/site-packages/django/core/management/__init__.py", line 194, in call_command
    return command.execute(*args, **defaults)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/michael/.venv/project/lib/python3.11/site-packages/django/core/management/base.py", line 459, in execute
    output = self.handle(*args, **options)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/michael/.venv/project/lib/python3.11/site-packages/django/contrib/staticfiles/management/commands/collectstatic.py", line 209, in handle
    collected = self.collect()
                ^^^^^^^^^^^^^^
  File "/home/michael/.venv/project/lib/python3.11/site-packages/django/contrib/staticfiles/management/commands/collectstatic.py", line 154, in collect
    raise processed
  File "/home/michael/.venv/project/lib/python3.11/site-packages/django/contrib/staticfiles/storage.py", line 375, in _post_process
    content = pattern.sub(converter, content)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/michael/.venv/project/lib/python3.11/site-packages/django/contrib/staticfiles/storage.py", line 249, in converter
    hashed_url = self._url(
                 ^^^^^^^^^^
  File "/home/michael/.venv/project/lib/python3.11/site-packages/django/contrib/staticfiles/storage.py", line 182, in _url
    hashed_name = hashed_name_func(*args)
                  ^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/michael/.venv/project/lib/python3.11/site-packages/django/contrib/staticfiles/storage.py", line 425, in _stored_name
    cache_name = self.clean_name(self.hashed_name(name))
                                 ^^^^^^^^^^^^^^^^^^^^^^
  File "/home/michael/.venv/project/lib/python3.11/site-packages/django/contrib/staticfiles/storage.py", line 143, in hashed_name
    raise ValueError(
ValueError: The file 'skin/skin/x-field.min.c6fe58e9f403.css' could not be found with <base.storage.LcManifestStaticFilesStorage object at 0x7f645c578a10>.

Even though the file exists in the correct collect static spot:

:/var/www/example.com/public/static/skin/skin$ ls -la x-field.min.css
-rw-r--r-- 1 michael www-data 6387 Apr 12 08:07 x-field.min.css

Change History (3)

comment:1 by Sarah Boyce, 3 weeks ago

Can you share what's in base.storage.LcManifestStaticFilesStorage here?

in reply to:  1 comment:2 by Michael, 3 weeks ago

Replying to Sarah Boyce:

Can you share what's in base.storage.LcManifestStaticFilesStorage here?

Sure:

from django.contrib.staticfiles.storage import ManifestStaticFilesStorage

class LcManifestStaticFilesStorage(ManifestStaticFilesStorage):
    support_js_module_import_aggregation = True

comment:3 by Sarah Boyce, 3 weeks ago

Summary: JS module export regex does not handle certain minified code correctlyFalse positive in JS module aggregation export regex when an export declaration precedes an import declaration
Triage Stage: UnreviewedAccepted
Type: UncategorizedBug
Version: 5.0dev

Thank you for the report! Replicated on main.

This can occur also when the JS is not minified.

For anyone wanting to pick this up. If we make the following update:

diff --git a/tests/staticfiles_tests/project/documents/cached/module.js b/tests/staticfiles_tests/project/documents/cached/module.js
index 7764e740d6..e9e61df56c 100644
--- a/tests/staticfiles_tests/project/documents/cached/module.js
+++ b/tests/staticfiles_tests/project/documents/cached/module.js
@@ -1,3 +1,4 @@
+export const moduleConst = "module";
 // Static imports.
 import rootConst from "/static/absolute_root.js";
 import testConst from "./module_test.js";

then tests for staticfiles_tests.test_storage.TestCollectionJSModuleImportAggregationManifestStorage fail with the reported error.

Note: See TracTickets for help on using tickets.
Back to Top