mirror of
https://github.com/LizardByte/Sunshine.git
synced 2026-10-23 03:28:06 +09:00
refactor(web): resolve CSS important rule violations (#5932)
This commit is contained in:
1 parent
a178f465e4
commit
2e4d4f5ac0
4 files changed
+313
-100
No files matched your search
@@ -8,24 +8,27 @@
|
||||
|
||||
<!-- Category Filter -->
|
||||
<div class="toolbar mb-4">
|
||||
<fieldset class="btn-group">
|
||||
<fieldset>
|
||||
<legend class="visually-hidden">{{ $t('featured.category_filter') }}</legend>
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary"
|
||||
:class="{ active: selectedCategory === null }"
|
||||
@click="selectedCategory = null">
|
||||
{{ $t('_common.all') }}
|
||||
</button>
|
||||
<button
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
type="button"
|
||||
class="btn btn-outline-primary"
|
||||
:class="{ active: selectedCategory === category.id }"
|
||||
@click="selectedCategory = category.id">
|
||||
{{ $t(`featured.categories.${category.originalId}`) }}
|
||||
</button>
|
||||
<!-- @brief Keep the legend outside Bootstrap's button group to preserve its outer corners. -->
|
||||
<div class="btn-group">
|
||||
<button
|
||||
type="button"
|
||||
class="btn btn-outline-primary"
|
||||
:class="{ active: selectedCategory === null }"
|
||||
@click="selectedCategory = null">
|
||||
{{ $t('_common.all') }}
|
||||
</button>
|
||||
<button
|
||||
v-for="category in categories"
|
||||
:key="category.id"
|
||||
type="button"
|
||||
class="btn btn-outline-primary"
|
||||
:class="{ active: selectedCategory === category.id }"
|
||||
@click="selectedCategory = category.id">
|
||||
{{ $t(`featured.categories.${category.originalId}`) }}
|
||||
</button>
|
||||
</div>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
@@ -67,7 +70,7 @@
|
||||
<h5 class="card-title mb-1">{{ app.name }}</h5>
|
||||
<p class="text-muted small mb-0" v-if="app.tagline">{{ app.tagline }}</p>
|
||||
</div>
|
||||
<span v-if="app.official" class="badge bg-primary flex-shrink-0 ms-2">{{ $t('featured.official') }}</span>
|
||||
<span v-if="app.official" class="badge badge-primary flex-shrink-0 ms-2">{{ $t('featured.official') }}</span>
|
||||
</div>
|
||||
|
||||
<p class="card-text text-muted small mb-3">{{ app.description }}</p>
|
||||
@@ -117,7 +120,7 @@
|
||||
|
||||
<!-- Platform Icons -->
|
||||
<div class="mb-3">
|
||||
<span class="badge bg-secondary me-1" v-for="platform in app.platforms" :key="platform">
|
||||
<span class="badge badge-secondary me-1" v-for="platform in app.platforms" :key="platform">
|
||||
<smartphone v-if="platform === 'android'" :size="14" class="icon"></smartphone>
|
||||
<gamepad2 v-else-if="platform === 'console'" :size="14" class="icon"></gamepad2>
|
||||
<gamepad v-else-if="platform === 'handheld'" :size="14" class="icon"></gamepad>
|
||||
|
||||
@@ -122,8 +122,8 @@ export default {
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Navbar toggler icon for dark text on light background */
|
||||
.navbar-sunshine .navbar-toggler-icon {
|
||||
--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.9%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e") !important;
|
||||
/** @brief Keep the Sunshine toggler icon above Bootstrap's light and dark variants. */
|
||||
.navbar-sunshine .navbar-toggler .navbar-toggler-icon {
|
||||
--bs-navbar-toggler-icon-bg: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 255, 255, 0.9%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
|
||||
}
|
||||
</style>
|
||||
@@ -2,6 +2,13 @@
|
||||
Sunshine UI - Modern Design System
|
||||
========================================================================== */
|
||||
|
||||
/**
|
||||
* @brief Theme Bootstrap components through source order and component selectors.
|
||||
*
|
||||
* Load this stylesheet after Bootstrap. Use badge-primary and badge-secondary
|
||||
* for themed badges so Bootstrap background utilities do not override them.
|
||||
*/
|
||||
|
||||
/* Hide pages while localization is loading */
|
||||
[v-cloak] {
|
||||
display: none;
|
||||
@@ -969,8 +976,8 @@ html {
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--color-bg-base) !important;
|
||||
color: var(--color-text-base) !important;
|
||||
background-color: var(--color-bg-base);
|
||||
color: var(--color-text-base);
|
||||
transition: background-color var(--transition-slow), color var(--transition-slow);
|
||||
min-height: 100vh;
|
||||
}
|
||||
@@ -993,11 +1000,11 @@ body {
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--color-surface) !important;
|
||||
border: 1px solid var(--color-border) !important;
|
||||
background-color: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow-sm);
|
||||
color: var(--color-text-base) !important;
|
||||
color: var(--color-text-base);
|
||||
transition: all var(--transition-base);
|
||||
}
|
||||
|
||||
@@ -1006,109 +1013,123 @@ body {
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: var(--color-bg-subtle) !important;
|
||||
border-color: var(--color-border) !important;
|
||||
color: var(--color-text-base) !important;
|
||||
background-color: var(--color-bg-subtle);
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
background-color: var(--color-bg-subtle) !important;
|
||||
border-color: var(--color-border) !important;
|
||||
background-color: var(--color-bg-subtle);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--color-surface) !important;
|
||||
border-color: var(--color-border) !important;
|
||||
background-color: var(--color-surface);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.modal-header,
|
||||
.modal-footer {
|
||||
border-color: var(--color-border) !important;
|
||||
background-color: var(--color-surface-raised) !important;
|
||||
border-color: var(--color-border);
|
||||
background-color: var(--color-surface-raised);
|
||||
}
|
||||
|
||||
.list-group-item {
|
||||
background-color: var(--color-surface) !important;
|
||||
border-color: var(--color-border) !important;
|
||||
color: var(--color-text-base) !important;
|
||||
--bs-list-group-disabled-bg: var(--color-surface);
|
||||
--bs-list-group-disabled-color: var(--color-text-base);
|
||||
--bs-list-group-action-hover-bg: var(--color-surface);
|
||||
--bs-list-group-action-hover-color: var(--color-text-base);
|
||||
--bs-list-group-action-active-bg: var(--color-surface);
|
||||
--bs-list-group-action-active-color: var(--color-text-base);
|
||||
--bs-list-group-active-bg: var(--color-surface);
|
||||
--bs-list-group-active-border-color: var(--color-border);
|
||||
--bs-list-group-active-color: var(--color-text-base);
|
||||
background-color: var(--color-surface);
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.list-group-item:hover {
|
||||
background-color: var(--color-bg-subtle) !important;
|
||||
.list-group-item:hover,
|
||||
.list-group-item-action:not(.active):hover {
|
||||
background-color: var(--color-bg-subtle);
|
||||
}
|
||||
|
||||
.accordion-item {
|
||||
background-color: var(--color-surface) !important;
|
||||
border-color: var(--color-border) !important;
|
||||
background-color: var(--color-surface);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.accordion-button {
|
||||
background-color: var(--color-bg-subtle) !important;
|
||||
color: var(--color-text-base) !important;
|
||||
background-color: var(--color-bg-subtle);
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.accordion-button:not(.collapsed) {
|
||||
background-color: var(--color-bg-muted) !important;
|
||||
color: var(--color-text-base) !important;
|
||||
background-color: var(--color-bg-muted);
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.offcanvas {
|
||||
background-color: var(--color-surface) !important;
|
||||
background-color: var(--color-surface);
|
||||
}
|
||||
|
||||
.offcanvas-header,
|
||||
.offcanvas-body {
|
||||
color: var(--color-text-base) !important;
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.toast {
|
||||
background-color: var(--color-surface) !important;
|
||||
border-color: var(--color-border) !important;
|
||||
background-color: var(--color-surface);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.toast-header {
|
||||
background-color: var(--color-bg-subtle) !important;
|
||||
border-color: var(--color-border) !important;
|
||||
color: var(--color-text-base) !important;
|
||||
background-color: var(--color-bg-subtle);
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.toast-body {
|
||||
color: var(--color-text-base) !important;
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.popover {
|
||||
background-color: var(--color-surface) !important;
|
||||
border-color: var(--color-border) !important;
|
||||
background-color: var(--color-surface);
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
.popover-header {
|
||||
background-color: var(--color-bg-subtle) !important;
|
||||
border-color: var(--color-border) !important;
|
||||
color: var(--color-text-base) !important;
|
||||
background-color: var(--color-bg-subtle);
|
||||
border-color: var(--color-border);
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.popover-body {
|
||||
color: var(--color-text-base) !important;
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.tooltip-inner {
|
||||
background-color: var(--color-bg-muted) !important;
|
||||
color: var(--color-text-base) !important;
|
||||
background-color: var(--color-bg-muted);
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.breadcrumb {
|
||||
background-color: var(--color-bg-subtle) !important;
|
||||
background-color: var(--color-bg-subtle);
|
||||
}
|
||||
|
||||
.breadcrumb-item a {
|
||||
color: var(--color-primary) !important;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.breadcrumb-item.active {
|
||||
color: var(--color-text-muted) !important;
|
||||
color: var(--color-text-muted);
|
||||
}
|
||||
|
||||
.pagination {
|
||||
--bs-pagination-hover-color: var(--color-text-base);
|
||||
--bs-pagination-focus-color: var(--color-text-base);
|
||||
--bs-pagination-active-color: var(--color-text-base);
|
||||
--bs-pagination-disabled-color: var(--color-text-base);
|
||||
--bs-pagination-bg: var(--color-surface);
|
||||
--bs-pagination-border-color: var(--color-border);
|
||||
--bs-pagination-hover-bg: var(--color-bg-subtle);
|
||||
@@ -1119,7 +1140,7 @@ body {
|
||||
}
|
||||
|
||||
.page-link {
|
||||
color: var(--color-text-base) !important;
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
@@ -1127,7 +1148,8 @@ body {
|
||||
========================================================================== */
|
||||
|
||||
.navbar-sunshine {
|
||||
background: var(--navbar-bg) !important;
|
||||
--bs-navbar-nav-link-padding-x: 1rem;
|
||||
background: var(--navbar-bg);
|
||||
box-shadow: var(--shadow-md);
|
||||
border: none;
|
||||
transition: all var(--transition-base);
|
||||
@@ -1142,9 +1164,9 @@ body {
|
||||
}
|
||||
|
||||
.navbar-sunshine .nav-link {
|
||||
color: var(--navbar-text-muted) !important;
|
||||
color: var(--navbar-text-muted);
|
||||
font-weight: 500;
|
||||
padding: 0.5rem 1rem !important;
|
||||
padding: 0.5rem 1rem;
|
||||
min-height: calc(1.5em + 1rem);
|
||||
border-radius: var(--radius-md);
|
||||
transition: all var(--transition-fast);
|
||||
@@ -1154,25 +1176,25 @@ body {
|
||||
}
|
||||
|
||||
.navbar-sunshine .nav-link:hover {
|
||||
color: var(--navbar-text) !important;
|
||||
color: var(--navbar-text);
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.navbar-sunshine .nav-link.active {
|
||||
color: var(--navbar-text) !important;
|
||||
color: var(--navbar-text);
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.navbar-sunshine .nav-link.dropdown-toggle.show {
|
||||
color: var(--navbar-text) !important;
|
||||
color: var(--navbar-text);
|
||||
background-color: rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
.navbar-sunshine .navbar-toggler {
|
||||
color: var(--navbar-text) !important;
|
||||
border-color: rgba(255, 255, 255, 0.2) !important;
|
||||
color: var(--navbar-text);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.navbar-sunshine .navbar-toggler:focus {
|
||||
@@ -1321,7 +1343,7 @@ p {
|
||||
.btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
transform: none !important;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@@ -1420,14 +1442,14 @@ p {
|
||||
Badge Styles
|
||||
========================================================================== */
|
||||
|
||||
.badge.bg-primary {
|
||||
background-color: var(--color-primary) !important;
|
||||
color: var(--color-on-primary) !important;
|
||||
.badge.badge-primary {
|
||||
background-color: var(--color-primary);
|
||||
color: var(--color-on-primary);
|
||||
}
|
||||
|
||||
.badge.bg-secondary {
|
||||
background-color: var(--color-bg-muted) !important;
|
||||
color: var(--color-text-base) !important;
|
||||
.badge.badge-secondary {
|
||||
background-color: var(--color-bg-muted);
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
/* ==========================================================================
|
||||
@@ -2242,7 +2264,7 @@ p {
|
||||
}
|
||||
|
||||
.cover-finder .cover-results.busy * {
|
||||
cursor: wait !important;
|
||||
cursor: wait;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -2313,8 +2335,8 @@ p {
|
||||
.markdown-body.release-notes {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
column-count: 1 !important;
|
||||
display: block !important;
|
||||
column-count: 1;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.markdown-body h1,
|
||||
@@ -2432,8 +2454,8 @@ p {
|
||||
========================================================================== */
|
||||
|
||||
.dropdown-menu {
|
||||
background-color: var(--color-surface) !important;
|
||||
border: 1px solid var(--color-border) !important;
|
||||
background-color: var(--color-surface);
|
||||
border: 1px solid var(--color-border);
|
||||
border-radius: var(--radius-md);
|
||||
box-shadow: var(--shadow-lg);
|
||||
padding: var(--spacing-sm);
|
||||
@@ -2446,27 +2468,31 @@ p {
|
||||
.dropdown-item {
|
||||
padding: var(--spacing-sm) var(--spacing-md);
|
||||
border-radius: var(--radius-sm);
|
||||
color: var(--color-text-base) !important;
|
||||
--bs-dropdown-link-hover-color: var(--color-text-base);
|
||||
--bs-dropdown-link-active-color: var(--color-text-base);
|
||||
--bs-dropdown-link-disabled-color: var(--color-text-base);
|
||||
color: var(--color-text-base);
|
||||
transition: all var(--transition-fast);
|
||||
}
|
||||
|
||||
.dropdown-item:hover {
|
||||
background-color: var(--color-bg-muted) !important;
|
||||
color: var(--color-text-base) !important;
|
||||
}
|
||||
|
||||
.dropdown-item.active {
|
||||
background-color: var(--color-primary-light);
|
||||
color: var(--color-primary);
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
[data-bs-theme="dark"] .dropdown-item.active {
|
||||
background-color: var(--color-primary);
|
||||
color: white;
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.dropdown-item:hover,
|
||||
.dropdown-menu .dropdown-item:hover {
|
||||
background-color: var(--color-bg-muted);
|
||||
color: var(--color-text-base);
|
||||
}
|
||||
|
||||
.dropdown-divider {
|
||||
border-color: var(--color-border) !important;
|
||||
border-color: var(--color-border);
|
||||
}
|
||||
|
||||
/* Add spacing between icons and text in dropdown items */
|
||||
@@ -2705,8 +2731,8 @@ p {
|
||||
}
|
||||
|
||||
.featured-app-icon img {
|
||||
max-width: 64px !important;
|
||||
max-height: 64px !important;
|
||||
max-width: 64px;
|
||||
max-height: 64px;
|
||||
width: auto;
|
||||
height: auto;
|
||||
object-fit: contain;
|
||||
@@ -2791,9 +2817,9 @@ p {
|
||||
}
|
||||
|
||||
.screenshot-thumbnail {
|
||||
height: 120px !important;
|
||||
height: 120px;
|
||||
width: auto;
|
||||
max-width: 200px !important;
|
||||
max-width: 200px;
|
||||
min-width: 100px;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
import { mount, flushPromises } from '@vue/test-utils'
|
||||
import { parse } from '@vue/compiler-sfc'
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { dirname, resolve } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'
|
||||
import navbarSource from '../../src_assets/common/assets/web/Navbar.vue?raw'
|
||||
|
||||
vi.mock('../../src_assets/common/assets/web/Navbar.vue', () => ({
|
||||
default: { template: '<div />' },
|
||||
}))
|
||||
|
||||
import Featured from '../../src_assets/common/assets/web/Featured.vue'
|
||||
|
||||
const navbarCss = parse(navbarSource).descriptor.styles[0].content
|
||||
const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..')
|
||||
const bootstrapCss = readFileSync(resolve(projectRoot, 'node_modules/bootstrap/dist/css/bootstrap.css'), 'utf8')
|
||||
const sunshineCss = readFileSync(resolve(projectRoot, 'src_assets/common/assets/web/sunshine.css'), 'utf8')
|
||||
let stylesheet
|
||||
let wrapper
|
||||
|
||||
beforeAll(() => {
|
||||
stylesheet = document.createElement('style')
|
||||
stylesheet.textContent = [bootstrapCss, sunshineCss, navbarCss].join('\n')
|
||||
document.head.append(stylesheet)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
wrapper?.unmount()
|
||||
wrapper = undefined
|
||||
document.body.replaceChildren()
|
||||
delete document.documentElement.dataset.theme
|
||||
delete document.documentElement.dataset.bsTheme
|
||||
vi.unstubAllGlobals()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
stylesheet.remove()
|
||||
})
|
||||
|
||||
/**
|
||||
* @brief Read the winning style with Bootstrap and the Sunshine theme loaded.
|
||||
*
|
||||
* @param {string} markup Component markup to insert into the document.
|
||||
* @param {string} selector Element whose computed styles should be returned.
|
||||
* @returns {CSSStyleDeclaration} Computed style, including unresolved theme variables in jsdom.
|
||||
*/
|
||||
function componentStyle(markup, selector) {
|
||||
document.body.innerHTML = markup
|
||||
return getComputedStyle(document.querySelector(selector))
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Mount the featured page with a deterministic app directory.
|
||||
*
|
||||
* @param {object} directory App and category data returned by the directory service.
|
||||
* @returns {Promise<import('@vue/test-utils').VueWrapper>} Mounted featured page.
|
||||
*/
|
||||
async function mountFeatured(directory) {
|
||||
vi.stubGlobal('fetch', vi.fn(async () => ({
|
||||
ok: true,
|
||||
json: async () => directory,
|
||||
})))
|
||||
wrapper = mount(Featured, {
|
||||
attachTo: document.body,
|
||||
global: { mocks: { $t: key => key }, stubs: { Navbar: true, SimpleIcon: true } },
|
||||
})
|
||||
await flushPromises()
|
||||
return wrapper
|
||||
}
|
||||
|
||||
const componentCases = [
|
||||
['body', '<div></div>', 'body', { backgroundColor: 'var(--color-bg-base)', color: 'var(--color-text-base)' }],
|
||||
['card', '<div class="card"></div>', '.card', { backgroundColor: 'var(--color-surface)', color: 'var(--color-text-base)' }],
|
||||
['card header', '<div class="card-header"></div>', '.card-header', { backgroundColor: 'var(--color-bg-subtle)', color: 'var(--color-text-base)' }],
|
||||
['card footer', '<div class="card-footer"></div>', '.card-footer', { backgroundColor: 'var(--color-bg-subtle)' }],
|
||||
['modal', '<div class="modal-content"></div>', '.modal-content', { backgroundColor: 'var(--color-surface)' }],
|
||||
['modal header', '<div class="modal-header"></div>', '.modal-header', { backgroundColor: 'var(--color-surface-raised)' }],
|
||||
['modal footer', '<div class="modal-footer"></div>', '.modal-footer', { backgroundColor: 'var(--color-surface-raised)' }],
|
||||
['list item', '<div class="list-group-item"></div>', '.list-group-item', { backgroundColor: 'var(--color-surface)', color: 'var(--color-text-base)' }],
|
||||
['accordion', '<div class="accordion-item"></div>', '.accordion-item', { backgroundColor: 'var(--color-surface)' }],
|
||||
['collapsed accordion button', '<button class="accordion-button collapsed"></button>', 'button', { backgroundColor: 'var(--color-bg-subtle)', color: 'var(--color-text-base)' }],
|
||||
['expanded accordion button', '<button class="accordion-button"></button>', 'button', { backgroundColor: 'var(--color-bg-muted)', color: 'var(--color-text-base)' }],
|
||||
['offcanvas', '<div class="offcanvas"></div>', '.offcanvas', { backgroundColor: 'var(--color-surface)' }],
|
||||
['offcanvas header', '<div class="offcanvas-header"></div>', '.offcanvas-header', { color: 'var(--color-text-base)' }],
|
||||
['offcanvas body', '<div class="offcanvas-body"></div>', '.offcanvas-body', { color: 'var(--color-text-base)' }],
|
||||
['toast', '<div class="toast"></div>', '.toast', { backgroundColor: 'var(--color-surface)' }],
|
||||
['toast header', '<div class="toast-header"></div>', '.toast-header', { backgroundColor: 'var(--color-bg-subtle)', color: 'var(--color-text-base)' }],
|
||||
['toast body', '<div class="toast-body"></div>', '.toast-body', { color: 'var(--color-text-base)' }],
|
||||
['popover', '<div class="popover"></div>', '.popover', { backgroundColor: 'var(--color-surface)' }],
|
||||
['popover header', '<div class="popover-header"></div>', '.popover-header', { backgroundColor: 'var(--color-bg-subtle)', color: 'var(--color-text-base)' }],
|
||||
['popover body', '<div class="popover-body"></div>', '.popover-body', { color: 'var(--color-text-base)' }],
|
||||
['tooltip', '<div class="tooltip-inner"></div>', '.tooltip-inner', { backgroundColor: 'var(--color-bg-muted)', color: 'var(--color-text-base)' }],
|
||||
['breadcrumb', '<ol class="breadcrumb"></ol>', '.breadcrumb', { backgroundColor: 'var(--color-bg-subtle)' }],
|
||||
['breadcrumb link', '<ol class="breadcrumb"><li class="breadcrumb-item"><a href="#">Page</a></li></ol>', 'a', { color: 'var(--color-primary)' }],
|
||||
['active breadcrumb', '<ol class="breadcrumb"><li class="breadcrumb-item active">Page</li></ol>', '.breadcrumb-item', { color: 'var(--color-text-muted)' }],
|
||||
['page link', '<ul class="pagination"><li class="page-item"><a class="page-link">1</a></li></ul>', '.page-link', { color: 'var(--color-text-base)' }],
|
||||
['navbar link', '<nav class="navbar navbar-expand-lg navbar-sunshine"><ul class="navbar-nav"><li><a class="nav-link">Page</a></li></ul></nav>', '.nav-link', { color: 'var(--navbar-text-muted)', paddingTop: '8px', paddingRight: '16px', paddingBottom: '8px', paddingLeft: '16px' }],
|
||||
['simple navbar theme control', '<nav class="navbar navbar-sunshine"><button class="nav-link">Theme</button></nav>', 'button', { color: 'var(--navbar-text-muted)', paddingTop: '8px', paddingRight: '16px', paddingBottom: '8px', paddingLeft: '16px' }],
|
||||
['active navbar link', '<nav class="navbar-sunshine"><ul class="navbar-nav"><li><a class="nav-link active">Page</a></li></ul></nav>', '.nav-link', { color: 'var(--navbar-text)' }],
|
||||
['open navbar dropdown', '<nav class="navbar-sunshine"><ul class="navbar-nav"><li><button class="nav-link dropdown-toggle show">Menu</button></li></ul></nav>', 'button', { color: 'var(--navbar-text)' }],
|
||||
['navbar toggler', '<nav class="navbar-sunshine"><button class="navbar-toggler"></button></nav>', 'button', { color: 'var(--navbar-text)', borderTopColor: 'rgba(255, 255, 255, 0.2)' }],
|
||||
['disabled button', '<button class="btn btn-primary" disabled>Save</button>', 'button', { transform: 'none' }],
|
||||
['busy cover', '<div class="cover-finder"><div class="cover-results busy"><button class="cover-container result"></button></div></div>', 'button', { cursor: 'wait', pointerEvents: 'none' }],
|
||||
['release notes', '<div class="markdown-body release-notes"></div>', '.release-notes', { columnCount: '1', display: 'block' }],
|
||||
['dropdown', '<div class="dropdown-menu"></div>', '.dropdown-menu', { backgroundColor: 'var(--color-surface)' }],
|
||||
['dropdown item', '<div class="dropdown-menu"><button class="dropdown-item">Theme</button></div>', 'button', { color: 'var(--color-text-base)' }],
|
||||
['active dropdown item', '<div class="dropdown-menu"><button class="dropdown-item active">Theme</button></div>', 'button', { color: 'var(--color-text-base)' }],
|
||||
['dropdown divider', '<div class="dropdown-menu"><hr class="dropdown-divider"></div>', 'hr', { opacity: '1' }],
|
||||
['featured icon', '<div class="featured-app-icon"><img alt="App"></div>', 'img', { maxWidth: '64px', maxHeight: '64px' }],
|
||||
['screenshot', '<button class="screenshot-thumbnail-button"><img class="screenshot-thumbnail" alt="Screenshot"></button>', 'img', { height: '120px', maxWidth: '200px' }],
|
||||
]
|
||||
|
||||
describe.each(['light', 'dark'])('%s theme cascade', theme => {
|
||||
it.each([0, 1, 2])('rounds the outside category filter corners with %i categories', async categoryCount => {
|
||||
document.documentElement.dataset.theme = theme
|
||||
document.documentElement.dataset.bsTheme = theme
|
||||
const categories = [
|
||||
{ id: 'clients', originalId: 'clients' },
|
||||
{ id: 'tools', originalId: 'tools' },
|
||||
].slice(0, categoryCount)
|
||||
await mountFeatured({ apps: [], categories })
|
||||
|
||||
expect(wrapper.get('fieldset legend').text()).toBe('featured.category_filter')
|
||||
const buttons = wrapper.findAll('.btn-group > .btn')
|
||||
expect(buttons).toHaveLength(categoryCount + 1)
|
||||
const firstStyle = getComputedStyle(buttons[0].element)
|
||||
const lastStyle = getComputedStyle(buttons.at(-1).element)
|
||||
expect(firstStyle.borderTopLeftRadius).not.toBe('0px')
|
||||
expect(firstStyle.borderBottomLeftRadius).not.toBe('0px')
|
||||
expect(lastStyle.borderTopRightRadius).not.toBe('0px')
|
||||
expect(lastStyle.borderBottomRightRadius).not.toBe('0px')
|
||||
if (categoryCount > 0) {
|
||||
expect(firstStyle.borderTopRightRadius).toBe('0px')
|
||||
expect(firstStyle.borderBottomRightRadius).toBe('0px')
|
||||
expect(lastStyle.borderTopLeftRadius).toBe('0px')
|
||||
expect(lastStyle.borderBottomLeftRadius).toBe('0px')
|
||||
for (const button of buttons.slice(1, -1)) {
|
||||
const style = getComputedStyle(button.element)
|
||||
expect([style.borderTopLeftRadius, style.borderBottomLeftRadius, style.borderTopRightRadius, style.borderBottomRightRadius]).toEqual(['0px', '0px', '0px', '0px'])
|
||||
}
|
||||
await buttons[1].trigger('click')
|
||||
expect(buttons[1].classes()).toContain('active')
|
||||
await buttons[0].trigger('click')
|
||||
expect(buttons[0].classes()).toContain('active')
|
||||
}
|
||||
})
|
||||
|
||||
it('allows specific component rules to override theme defaults', () => {
|
||||
document.documentElement.dataset.theme = theme
|
||||
document.documentElement.dataset.bsTheme = theme
|
||||
const override = document.createElement('style')
|
||||
override.textContent = '.card.custom-card { background-color: rgb(1, 2, 3); color: rgb(4, 5, 6); }'
|
||||
document.head.append(override)
|
||||
try {
|
||||
const style = componentStyle('<div class="card custom-card"></div>', '.card')
|
||||
expect(style.backgroundColor).toBe('rgb(1, 2, 3)')
|
||||
expect(style.color).toBe('rgb(4, 5, 6)')
|
||||
} finally {
|
||||
override.remove()
|
||||
}
|
||||
})
|
||||
|
||||
it.each(componentCases)('preserves %s styles', (_name, markup, selector, expected) => {
|
||||
document.documentElement.dataset.theme = theme
|
||||
document.documentElement.dataset.bsTheme = theme
|
||||
const style = componentStyle(markup, selector)
|
||||
for (const [property, value] of Object.entries(expected)) {
|
||||
expect(style[property], property).toBe(value)
|
||||
}
|
||||
})
|
||||
|
||||
it('themes the rendered official and platform badges without Bootstrap background utilities', async () => {
|
||||
document.documentElement.dataset.theme = theme
|
||||
document.documentElement.dataset.bsTheme = theme
|
||||
await mountFeatured({ apps: [{ id: 'sample', name: 'Sample App', official: true, platforms: ['linux'] }] })
|
||||
|
||||
const [official, platform] = wrapper.findAll('.badge')
|
||||
expect(getComputedStyle(official.element).backgroundColor).toBe('var(--color-primary)')
|
||||
expect(getComputedStyle(official.element).color).toBe('var(--color-on-primary)')
|
||||
expect(getComputedStyle(platform.element).backgroundColor).toBe('var(--color-bg-muted)')
|
||||
expect(getComputedStyle(platform.element).color).toBe('var(--color-text-base)')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user