diff --git a/superset-frontend/webpack.config.js b/superset-frontend/webpack.config.js
index a4c1b86b482eb..6b3c4e30a8b13 100644
--- a/superset-frontend/webpack.config.js
+++ b/superset-frontend/webpack.config.js
@@ -95,10 +95,10 @@ const plugins = [
entryFiles[entry] = {
css: chunks
.filter(x => x.endsWith('.css'))
- .map(x => path.join(output.publicPath, x)),
+ .map(x => `${output.publicPath}${x}`),
js: chunks
.filter(x => x.endsWith('.js'))
- .map(x => path.join(output.publicPath, x)),
+ .map(x => `${output.publicPath}${x}`),
};
});
diff --git a/superset/common/query_object.py b/superset/common/query_object.py
index 2a40155d1ca4f..fd988a36fac05 100644
--- a/superset/common/query_object.py
+++ b/superset/common/query_object.py
@@ -31,7 +31,7 @@
QueryObjectValidationError,
)
from superset.sql_parse import validate_filter_clause
-from superset.typing import Column, Metric, OrderBy
+from superset.superset_typing import Column, Metric, OrderBy
from superset.utils import pandas_postprocessing
from superset.utils.core import (
DTTM_ALIAS,
diff --git a/superset/config.py b/superset/config.py
index 6579719ea81cc..a7c03945fd10c 100644
--- a/superset/config.py
+++ b/superset/config.py
@@ -45,7 +45,7 @@
from superset.constants import CHANGE_ME_SECRET_KEY
from superset.jinja_context import BaseTemplateProcessor
from superset.stats_logger import DummyStatsLogger
-from superset.typing import CacheConfig
+from superset.superset_typing import CacheConfig
from superset.utils.core import is_test, parse_boolean_string
from superset.utils.encrypt import SQLAlchemyUtilsAdapter
from superset.utils.log import DBEventLogger
@@ -1249,6 +1249,10 @@ def SQL_QUERY_MUTATOR( # pylint: disable=invalid-name,unused-argument
# SQLALCHEMY_DATABASE_URI by default if set to `None`
SQLALCHEMY_EXAMPLES_URI = None
+# Optional prefix to be added to all static asset paths when rendering the UI.
+# This is useful for hosting assets in an external CDN, for example
+STATIC_ASSETS_PREFIX = ""
+
# Some sqlalchemy connection strings can open Superset to security risks.
# Typically these should not be allowed.
PREVENT_UNSAFE_DB_CONNECTIONS = True
diff --git a/superset/connectors/base/models.py b/superset/connectors/base/models.py
index 967235f328c2e..5cf2a8719bf95 100644
--- a/superset/connectors/base/models.py
+++ b/superset/connectors/base/models.py
@@ -29,7 +29,7 @@
from superset.datasets.commands.exceptions import DatasetNotFoundError
from superset.models.helpers import AuditMixinNullable, ImportExportMixin, QueryResult
from superset.models.slice import Slice
-from superset.typing import FilterValue, FilterValues, QueryObjectDict
+from superset.superset_typing import FilterValue, FilterValues, QueryObjectDict
from superset.utils import core as utils
from superset.utils.core import GenericDataType
diff --git a/superset/connectors/druid/models.py b/superset/connectors/druid/models.py
index 32edb695279c0..3a17ec5319374 100644
--- a/superset/connectors/druid/models.py
+++ b/superset/connectors/druid/models.py
@@ -58,7 +58,7 @@
from superset.extensions import encrypted_field_factory
from superset.models.core import Database
from superset.models.helpers import AuditMixinNullable, ImportExportMixin, QueryResult
-from superset.typing import (
+from superset.superset_typing import (
AdhocMetric,
AdhocMetricColumn,
FilterValues,
diff --git a/superset/connectors/druid/views.py b/superset/connectors/druid/views.py
index 03a3a42ec08cc..cd7e5d279ba25 100644
--- a/superset/connectors/druid/views.py
+++ b/superset/connectors/druid/views.py
@@ -34,7 +34,7 @@
from superset.connectors.connector_registry import ConnectorRegistry
from superset.connectors.druid import models
from superset.constants import RouteMethod
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from superset.utils import core as utils
from superset.views.base import (
BaseSupersetView,
diff --git a/superset/connectors/sqla/models.py b/superset/connectors/sqla/models.py
index 9cc2f8a78136b..2f466bc681dc2 100644
--- a/superset/connectors/sqla/models.py
+++ b/superset/connectors/sqla/models.py
@@ -96,8 +96,14 @@
QueryResult,
)
from superset.sql_parse import ParsedQuery
+from superset.superset_typing import (
+ AdhocColumn,
+ AdhocMetric,
+ Metric,
+ OrderBy,
+ QueryObjectDict,
+)
from superset.tables.models import Table as NewTable
-from superset.typing import AdhocColumn, AdhocMetric, Metric, OrderBy, QueryObjectDict
from superset.utils import core as utils
from superset.utils.core import (
GenericDataType,
diff --git a/superset/connectors/sqla/views.py b/superset/connectors/sqla/views.py
index fef8a2d8a4356..a16ffa49f62ba 100644
--- a/superset/connectors/sqla/views.py
+++ b/superset/connectors/sqla/views.py
@@ -36,7 +36,7 @@
from superset.connectors.base.views import DatasourceModelView
from superset.connectors.sqla import models
from superset.constants import MODEL_VIEW_RW_METHOD_PERMISSION_MAP, RouteMethod
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from superset.utils import core as utils
from superset.views.base import (
check_ownership,
diff --git a/superset/databases/api.py b/superset/databases/api.py
index 1b8b408c1ca91..eea5c9979fa4d 100644
--- a/superset/databases/api.py
+++ b/superset/databases/api.py
@@ -70,7 +70,7 @@
from superset.errors import ErrorLevel, SupersetError, SupersetErrorType
from superset.extensions import security_manager
from superset.models.core import Database
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from superset.utils.core import error_msg_from_exception
from superset.views.base_api import (
BaseSupersetModelRestApi,
diff --git a/superset/extensions.py b/superset/extensions.py
index 33dc1706a6b78..742182b078d1b 100644
--- a/superset/extensions.py
+++ b/superset/extensions.py
@@ -63,22 +63,26 @@ def init_app(self, app: Flask) -> None:
self.app = app
# Preload the cache
self.parse_manifest_json()
-
- @app.context_processor
- def get_manifest() -> Dict[str, Callable[[str], List[str]]]:
- loaded_chunks = set()
-
- def get_files(bundle: str, asset_type: str = "js") -> List[str]:
- files = self.get_manifest_files(bundle, asset_type)
- filtered_files = [f for f in files if f not in loaded_chunks]
- for f in filtered_files:
- loaded_chunks.add(f)
- return filtered_files
-
- return dict(
- js_manifest=lambda bundle: get_files(bundle, "js"),
- css_manifest=lambda bundle: get_files(bundle, "css"),
- )
+ self.register_processor(app)
+
+ def register_processor(self, app: Flask) -> None:
+ app.template_context_processors[None].append(self.get_manifest)
+
+ def get_manifest(self) -> Dict[str, Callable[[str], List[str]]]:
+ loaded_chunks = set()
+
+ def get_files(bundle: str, asset_type: str = "js") -> List[str]:
+ files = self.get_manifest_files(bundle, asset_type)
+ filtered_files = [f for f in files if f not in loaded_chunks]
+ for f in filtered_files:
+ loaded_chunks.add(f)
+ return filtered_files
+
+ return dict(
+ js_manifest=lambda bundle: get_files(bundle, "js"),
+ css_manifest=lambda bundle: get_files(bundle, "css"),
+ assets_prefix=self.app.config["STATIC_ASSETS_PREFIX"] if self.app else "",
+ )
def parse_manifest_json(self) -> None:
try:
diff --git a/superset/initialization/__init__.py b/superset/initialization/__init__.py
index 59e204d6d65e8..94d905b2d0bf6 100644
--- a/superset/initialization/__init__.py
+++ b/superset/initialization/__init__.py
@@ -49,7 +49,7 @@
talisman,
)
from superset.security import SupersetSecurityManager
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from superset.utils.core import pessimistic_connection_handling
from superset.utils.log import DBEventLogger, get_event_logger_from_cfg_value
diff --git a/superset/result_set.py b/superset/result_set.py
index b95b5e680d7db..19035b6d23788 100644
--- a/superset/result_set.py
+++ b/superset/result_set.py
@@ -26,7 +26,7 @@
import pyarrow as pa
from superset.db_engine_specs import BaseEngineSpec
-from superset.typing import DbapiDescription, DbapiResult
+from superset.superset_typing import DbapiDescription, DbapiResult
from superset.utils import core as utils
logger = logging.getLogger(__name__)
diff --git a/superset/typing.py b/superset/superset_typing.py
similarity index 100%
rename from superset/typing.py
rename to superset/superset_typing.py
diff --git a/superset/templates/superset/base.html b/superset/templates/superset/base.html
index a861c659e7034..e3c3d35dfe503 100644
--- a/superset/templates/superset/base.html
+++ b/superset/templates/superset/base.html
@@ -21,7 +21,7 @@
{% block head_css %}
{{ super() }}
-
+
{{ css_bundle("theme") }}
{% endblock %}
diff --git a/superset/templates/superset/basic.html b/superset/templates/superset/basic.html
index 902fc8c328de4..fff57fdb9fa18 100644
--- a/superset/templates/superset/basic.html
+++ b/superset/templates/superset/basic.html
@@ -40,11 +40,11 @@
rel="{{favicon.rel if favicon.rel else "icon"}}"
type="{{favicon.type if favicon.type else "image/png"}}"
{% if favicon.sizes %}sizes={{favicon.sizes}}{% endif %}
- href="{{favicon.href}}"
+ href="{{ assets_prefix }}{{favicon.href}}"
>
{% endfor %}
-
-
+
+
{{ css_bundle("theme") }}
@@ -73,7 +73,7 @@
{% block body %}
-
+
{% endblock %}
diff --git a/superset/templates/superset/theme.html b/superset/templates/superset/theme.html
index feac56f895980..856796a4c4b21 100644
--- a/superset/templates/superset/theme.html
+++ b/superset/templates/superset/theme.html
@@ -1342,5 +1342,5 @@ Source Code
{{ super() }}
-
+
{% endblock %}
diff --git a/superset/utils/core.py b/superset/utils/core.py
index 2fdbc278adb70..36d59333d2ed3 100644
--- a/superset/utils/core.py
+++ b/superset/utils/core.py
@@ -98,7 +98,7 @@
SupersetException,
SupersetTimeoutException,
)
-from superset.typing import (
+from superset.superset_typing import (
AdhocColumn,
AdhocMetric,
AdhocMetricColumn,
diff --git a/superset/views/alerts.py b/superset/views/alerts.py
index e96f701c3d1b7..416966fbe7c35 100644
--- a/superset/views/alerts.py
+++ b/superset/views/alerts.py
@@ -30,8 +30,8 @@
from superset import is_feature_enabled
from superset.constants import RouteMethod
from superset.models.alerts import Alert, AlertLog, SQLObservation
+from superset.superset_typing import FlaskResponse
from superset.tasks.alerts.validator import check_validator
-from superset.typing import FlaskResponse
from superset.utils import core as utils
from superset.utils.core import get_email_address_str, markdown
diff --git a/superset/views/annotations.py b/superset/views/annotations.py
index 4fa83c0ca4be4..dc1df5642af35 100644
--- a/superset/views/annotations.py
+++ b/superset/views/annotations.py
@@ -26,7 +26,7 @@
from superset import is_feature_enabled
from superset.constants import MODEL_VIEW_RW_METHOD_PERMISSION_MAP, RouteMethod
from superset.models.annotations import Annotation, AnnotationLayer
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from superset.views.base import SupersetModelView
diff --git a/superset/views/api.py b/superset/views/api.py
index d4d94ce72346c..bde25236460da 100644
--- a/superset/views/api.py
+++ b/superset/views/api.py
@@ -31,7 +31,7 @@
)
from superset.legacy import update_time_range
from superset.models.slice import Slice
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from superset.utils import core as utils
from superset.utils.date_parser import get_since_until
from superset.views.base import api, BaseSupersetView, handle_api_exception
diff --git a/superset/views/base.py b/superset/views/base.py
index 1249bc43cc4fb..3024c4490d167 100644
--- a/superset/views/base.py
+++ b/superset/views/base.py
@@ -73,8 +73,8 @@
)
from superset.models.helpers import ImportExportMixin
from superset.models.reports import ReportRecipientType
+from superset.superset_typing import FlaskResponse
from superset.translations.utils import get_language_pack
-from superset.typing import FlaskResponse
from superset.utils import core as utils
from .utils import bootstrap_user_data
diff --git a/superset/views/base_api.py b/superset/views/base_api.py
index 87e99e7c74a7b..260e5731788bc 100644
--- a/superset/views/base_api.py
+++ b/superset/views/base_api.py
@@ -37,7 +37,7 @@
from superset.schemas import error_payload_content
from superset.sql_lab import Query as SqllabQuery
from superset.stats_logger import BaseStatsLogger
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from superset.utils.core import time_function
logger = logging.getLogger(__name__)
diff --git a/superset/views/chart/views.py b/superset/views/chart/views.py
index 37ef9a043e881..9ecc69f7b9e8e 100644
--- a/superset/views/chart/views.py
+++ b/superset/views/chart/views.py
@@ -24,7 +24,7 @@
from superset import is_feature_enabled
from superset.constants import MODEL_VIEW_RW_METHOD_PERMISSION_MAP, RouteMethod
from superset.models.slice import Slice
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from superset.utils import core as utils
from superset.views.base import (
check_ownership,
diff --git a/superset/views/core.py b/superset/views/core.py
index f69ee77bef2f0..2263320a403c5 100755
--- a/superset/views/core.py
+++ b/superset/views/core.py
@@ -118,8 +118,8 @@
from superset.sqllab.sqllab_execution_context import SqlJsonExecutionContext
from superset.sqllab.utils import apply_display_max_row_configuration_if_require
from superset.sqllab.validators import CanAccessQueryValidatorImpl
+from superset.superset_typing import FlaskResponse
from superset.tasks.async_queries import load_explore_json_into_cache
-from superset.typing import FlaskResponse
from superset.utils import core as utils, csv
from superset.utils.async_query_manager import AsyncQueryTokenException
from superset.utils.cache import etag_cache
diff --git a/superset/views/css_templates.py b/superset/views/css_templates.py
index d26acea5cfac7..2cfbd43ae962a 100644
--- a/superset/views/css_templates.py
+++ b/superset/views/css_templates.py
@@ -22,7 +22,7 @@
from superset import is_feature_enabled
from superset.constants import MODEL_VIEW_RW_METHOD_PERMISSION_MAP, RouteMethod
from superset.models import core as models
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from superset.views.base import DeleteMixin, SupersetModelView
diff --git a/superset/views/dashboard/views.py b/superset/views/dashboard/views.py
index 99782def38a68..49ba61d08e0d2 100644
--- a/superset/views/dashboard/views.py
+++ b/superset/views/dashboard/views.py
@@ -29,7 +29,7 @@
from superset import db, event_logger, is_feature_enabled, security_manager
from superset.constants import MODEL_VIEW_RW_METHOD_PERMISSION_MAP, RouteMethod
from superset.models.dashboard import Dashboard as DashboardModel
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from superset.utils import core as utils
from superset.views.base import (
BaseSupersetView,
diff --git a/superset/views/database/views.py b/superset/views/database/views.py
index 115d168ed636a..aea4e04383570 100644
--- a/superset/views/database/views.py
+++ b/superset/views/database/views.py
@@ -37,7 +37,7 @@
from superset.exceptions import CertificateException
from superset.extensions import event_logger
from superset.sql_parse import Table
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from superset.utils import core as utils
from superset.views.base import DeleteMixin, SupersetModelView, YamlExportMixin
diff --git a/superset/views/datasource/views.py b/superset/views/datasource/views.py
index e2cb204082dd6..7e1ffa0468e90 100644
--- a/superset/views/datasource/views.py
+++ b/superset/views/datasource/views.py
@@ -38,7 +38,7 @@
from superset.exceptions import SupersetException, SupersetSecurityException
from superset.extensions import security_manager
from superset.models.core import Database
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from superset.views.base import (
api,
BaseSupersetView,
diff --git a/superset/views/health.py b/superset/views/health.py
index 876e7a5e130be..cf85b8927899d 100644
--- a/superset/views/health.py
+++ b/superset/views/health.py
@@ -15,7 +15,7 @@
# specific language governing permissions and limitations
# under the License.
from superset import app, talisman
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
@talisman(force_https=False)
diff --git a/superset/views/key_value.py b/superset/views/key_value.py
index 8f8aa99787a21..da39f094b812f 100644
--- a/superset/views/key_value.py
+++ b/superset/views/key_value.py
@@ -23,7 +23,7 @@
from superset import db, event_logger, is_feature_enabled
from superset.models import core as models
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from superset.utils import core as utils
from superset.views.base import BaseSupersetView, json_error_response
diff --git a/superset/views/redirects.py b/superset/views/redirects.py
index da9613f3a0bb8..6b1510d8f5778 100644
--- a/superset/views/redirects.py
+++ b/superset/views/redirects.py
@@ -24,7 +24,7 @@
from superset import db, event_logger
from superset.models import core as models
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from superset.views.base import BaseSupersetView
logger = logging.getLogger(__name__)
diff --git a/superset/views/schedules.py b/superset/views/schedules.py
index d1c59f413c839..39d4af9b8b259 100644
--- a/superset/views/schedules.py
+++ b/superset/views/schedules.py
@@ -42,8 +42,8 @@
SliceEmailSchedule,
)
from superset.models.slice import Slice
+from superset.superset_typing import FlaskResponse
from superset.tasks.schedules import schedule_email_report
-from superset.typing import FlaskResponse
from superset.utils.core import get_email_address_list, json_iso_dttm_ser
from superset.views.core import json_success
diff --git a/superset/views/sql_lab.py b/superset/views/sql_lab.py
index 6a5ce26d38ad9..5ec525b9cac73 100644
--- a/superset/views/sql_lab.py
+++ b/superset/views/sql_lab.py
@@ -24,7 +24,7 @@
from superset import db, is_feature_enabled
from superset.constants import MODEL_VIEW_RW_METHOD_PERMISSION_MAP, RouteMethod
from superset.models.sql_lab import Query, SavedQuery, TableSchema, TabState
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from superset.utils import core as utils
from .base import BaseSupersetView, DeleteMixin, json_success, SupersetModelView
diff --git a/superset/views/tags.py b/superset/views/tags.py
index c6fac2ff77145..8ab2798f5d84c 100644
--- a/superset/views/tags.py
+++ b/superset/views/tags.py
@@ -33,7 +33,7 @@
from superset.models.slice import Slice
from superset.models.sql_lab import SavedQuery
from superset.models.tags import ObjectTypes, Tag, TaggedObject, TagTypes
-from superset.typing import FlaskResponse
+from superset.superset_typing import FlaskResponse
from .base import BaseSupersetView, json_success
diff --git a/superset/views/utils.py b/superset/views/utils.py
index 17ec6ea1088c9..62639174f647e 100644
--- a/superset/views/utils.py
+++ b/superset/views/utils.py
@@ -46,7 +46,7 @@
from superset.models.dashboard import Dashboard
from superset.models.slice import Slice
from superset.models.sql_lab import Query
-from superset.typing import FormData
+from superset.superset_typing import FormData
from superset.utils.decorators import stats_timing
from superset.viz import BaseViz
diff --git a/superset/viz.py b/superset/viz.py
index 9a1086442be62..7c0f8e134875b 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -70,7 +70,13 @@
from superset.extensions import cache_manager, security_manager
from superset.models.helpers import QueryResult
from superset.sql_parse import validate_filter_clause
-from superset.typing import Column, Metric, QueryObjectDict, VizData, VizPayload
+from superset.superset_typing import (
+ Column,
+ Metric,
+ QueryObjectDict,
+ VizData,
+ VizPayload,
+)
from superset.utils import core as utils, csv
from superset.utils.cache import set_and_log_cache
from superset.utils.core import (
diff --git a/tests/integration_tests/charts/data/api_tests.py b/tests/integration_tests/charts/data/api_tests.py
index 4f63ad51b65d7..bc8ec74feb0d6 100644
--- a/tests/integration_tests/charts/data/api_tests.py
+++ b/tests/integration_tests/charts/data/api_tests.py
@@ -47,7 +47,7 @@
from superset.extensions import async_query_manager, db
from superset.models.annotations import AnnotationLayer
from superset.models.slice import Slice
-from superset.typing import AdhocColumn
+from superset.superset_typing import AdhocColumn
from superset.utils.core import (
AnnotationType,
get_example_default_schema,
diff --git a/tests/unit_tests/dataframe_test.py b/tests/unit_tests/dataframe_test.py
index 3e986a5e43a7f..79625cffe63de 100644
--- a/tests/unit_tests/dataframe_test.py
+++ b/tests/unit_tests/dataframe_test.py
@@ -16,7 +16,7 @@
# under the License.
# pylint: disable=unused-argument, import-outside-toplevel
from superset.dataframe import df_to_records
-from superset.typing import DbapiDescription
+from superset.superset_typing import DbapiDescription
def test_df_to_records(app_context: None) -> None:
diff --git a/tests/unit_tests/extension_tests.py b/tests/unit_tests/extension_tests.py
new file mode 100644
index 0000000000000..724b03f01a2ab
--- /dev/null
+++ b/tests/unit_tests/extension_tests.py
@@ -0,0 +1,51 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+from os.path import dirname
+from unittest.mock import Mock
+
+from superset.extensions import UIManifestProcessor
+
+APP_DIR = f"{dirname(__file__)}/fixtures"
+
+
+def test_get_manifest_with_prefix():
+ app = Mock(
+ config={"STATIC_ASSETS_PREFIX": "https://cool.url/here"},
+ template_context_processors={None: []},
+ )
+ manifest_processor = UIManifestProcessor(APP_DIR)
+ manifest_processor.init_app(app)
+ manifest = manifest_processor.get_manifest()
+ assert manifest["js_manifest"]("main") == ["/static/dist/main-js.js"]
+ assert manifest["css_manifest"]("main") == ["/static/dist/main-css.css"]
+ assert manifest["js_manifest"]("styles") == ["/static/dist/styles-js.js"]
+ assert manifest["css_manifest"]("styles") == []
+ assert manifest["assets_prefix"] == "https://cool.url/here"
+
+
+def test_get_manifest_no_prefix():
+ app = Mock(
+ config={"STATIC_ASSETS_PREFIX": ""}, template_context_processors={None: []}
+ )
+ manifest_processor = UIManifestProcessor(APP_DIR)
+ manifest_processor.init_app(app)
+ manifest = manifest_processor.get_manifest()
+ assert manifest["js_manifest"]("main") == ["/static/dist/main-js.js"]
+ assert manifest["css_manifest"]("main") == ["/static/dist/main-css.css"]
+ assert manifest["js_manifest"]("styles") == ["/static/dist/styles-js.js"]
+ assert manifest["css_manifest"]("styles") == []
+ assert manifest["assets_prefix"] == ""
diff --git a/tests/unit_tests/fixtures/static/assets/manifest.json b/tests/unit_tests/fixtures/static/assets/manifest.json
new file mode 100644
index 0000000000000..7482a04eac74e
--- /dev/null
+++ b/tests/unit_tests/fixtures/static/assets/manifest.json
@@ -0,0 +1,20 @@
+{
+ "entrypoints": {
+ "styles": {
+ "js": [
+ "/static/dist/styles-js.js"
+ ]
+ },
+ "main": {
+ "css": [
+ "/static/dist/main-css.css"
+ ],
+ "js": [
+ "/static/dist/main-js.js"
+ ]
+ }
+ },
+ "main.css": "/static/dist/main.b51d3f6225194da423d6.entry.css",
+ "main.js": "/static/dist/main.b51d3f6225194da423d6.entry.js",
+ "styles.js": "/static/dist/styles.35840b4bbf794f902b7c.entry.js"
+}