mirror of
https://github.com/sky-systems/sky_phone.git
synced 2026-09-04 17:23:25 +00:00
ENH - merge Picstagram Sky UI updates
Merge the Picstagram Sky UI work and its dependent phone, media, voice, gallery, app store, setup, and UI parity updates into weather-app. Resolve the frontend development port configuration and retain the Sky UI EasyShare implementation. Frontend validation and local resource deployment completed; included configuration, locale, and SQL updates require no separate migration step beyond this merge.
This commit is contained in:
@@ -16,17 +16,23 @@ import {
|
||||
reorderDirectionFromKeyboard,
|
||||
type ReorderDirection,
|
||||
} from '@/utils/keyboard'
|
||||
import {
|
||||
springboardPageDragCompensation,
|
||||
springboardSwipeIntent,
|
||||
} from '@/utils/springboardDrag'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
app: PhoneAppDefinition
|
||||
compact?: boolean
|
||||
editMode?: boolean
|
||||
externalDragVisual?: boolean
|
||||
showLabel?: boolean
|
||||
}>(),
|
||||
{
|
||||
compact: false,
|
||||
editMode: false,
|
||||
externalDragVisual: false,
|
||||
showLabel: true,
|
||||
},
|
||||
)
|
||||
@@ -54,10 +60,16 @@ const dragOffset = ref({ x: 0, y: 0 })
|
||||
let dragStartPage = 0
|
||||
let dragPageWidth = 0
|
||||
const dragStyle = computed(() =>
|
||||
isDragging.value
|
||||
isDragging.value && !props.externalDragVisual
|
||||
? {
|
||||
transform: `translateX(${(phone.currentPage - dragStartPage) * dragPageWidth}px)`,
|
||||
translate: `${dragOffset.value.x}px ${dragOffset.value.y}px`,
|
||||
transform: `translate3d(${springboardPageDragCompensation(dragStartPage, phone.currentPage, dragPageWidth)}px, 0, 0)`,
|
||||
}
|
||||
: undefined,
|
||||
)
|
||||
const dragPointerStyle = computed(() =>
|
||||
isDragging.value && !props.externalDragVisual
|
||||
? {
|
||||
transform: `translate3d(${dragOffset.value.x}px, ${dragOffset.value.y}px, 0)`,
|
||||
}
|
||||
: undefined,
|
||||
)
|
||||
@@ -142,6 +154,7 @@ function clearHold(): void {
|
||||
|
||||
function onPointerDown(event: PointerEvent): void {
|
||||
if (props.compact || event.button !== 0) return
|
||||
suppressClick.value = false
|
||||
pointerTarget = event.currentTarget as HTMLElement
|
||||
pointerId = event.pointerId
|
||||
pointerTarget.setPointerCapture(pointerId)
|
||||
@@ -169,9 +182,12 @@ function onPointerMove(event: PointerEvent): void {
|
||||
return
|
||||
}
|
||||
if (
|
||||
Math.hypot(event.clientX - pointerStart.x, event.clientY - pointerStart.y) >
|
||||
8
|
||||
springboardSwipeIntent(
|
||||
event.clientX - pointerStart.x,
|
||||
event.clientY - pointerStart.y,
|
||||
) !== 'pending'
|
||||
) {
|
||||
suppressClick.value = true
|
||||
clearHold()
|
||||
}
|
||||
}
|
||||
@@ -190,9 +206,11 @@ function onPointerUp(event: PointerEvent): void {
|
||||
clearHold()
|
||||
if (isDragging.value) {
|
||||
suppressClick.value = true
|
||||
emit('dragend', event)
|
||||
isDragging.value = false
|
||||
dragOffset.value = { x: 0, y: 0 }
|
||||
releasePointerCapture()
|
||||
emit('dragend', event)
|
||||
return
|
||||
}
|
||||
releasePointerCapture()
|
||||
}
|
||||
@@ -237,7 +255,7 @@ onMounted(() => {
|
||||
onBeforeUnmount(() => {
|
||||
clearHold()
|
||||
if (calendarTimer !== undefined) window.clearInterval(calendarTimer)
|
||||
releasePointerCapture()
|
||||
cancelPointerDrag()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -246,6 +264,7 @@ onBeforeUnmount(() => {
|
||||
class="app-icon-item"
|
||||
:class="{
|
||||
'app-icon-item--compact': compact,
|
||||
'app-icon-item--drag-source': isDragging && externalDragVisual,
|
||||
'app-icon-item--dragging': isDragging,
|
||||
'app-icon-item--editing': editMode,
|
||||
}"
|
||||
@@ -260,6 +279,7 @@ onBeforeUnmount(() => {
|
||||
:aria-keyshortcuts="
|
||||
editMode ? 'ArrowLeft ArrowRight ArrowUp ArrowDown' : undefined
|
||||
"
|
||||
:style="dragPointerStyle"
|
||||
@click="launch"
|
||||
@contextmenu.prevent
|
||||
@keydown="onKeydown"
|
||||
@@ -319,6 +339,7 @@ onBeforeUnmount(() => {
|
||||
app: getPhoneAppLabel(app, phone.t),
|
||||
})
|
||||
"
|
||||
:style="dragPointerStyle"
|
||||
@click.stop="emit('remove')"
|
||||
@pointerdown.stop
|
||||
>
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const source = readFileSync(
|
||||
new URL('./EasyShareSheet.vue', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
|
||||
describe('EasyShareSheet Sky UI contract', () => {
|
||||
it('uses the first-party bottom sheet without Konsta markup', () => {
|
||||
expect(source).not.toContain("from 'konsta/vue'")
|
||||
expect(source).not.toMatch(/<\/?k-[a-z]/)
|
||||
expect(source).toContain('<SkySheet')
|
||||
expect(source).toContain('<SkyList')
|
||||
expect(source).toContain('<SkyGlass')
|
||||
expect(source).toContain('.easyshare-host :deep(.sky-sheet__panel)')
|
||||
expect(source).toMatch(/--easyshare-solid-surface:\s*#1c1c1d/)
|
||||
expect(source).toMatch(
|
||||
/background:\s*var\(--easyshare-solid-surface\)\s*!important/,
|
||||
)
|
||||
expect(source).toMatch(/--easyshare-list-surface:\s*#2c2c2e/)
|
||||
expect(source).toMatch(
|
||||
/\.easyshare-history\s*\{[^}]*overflow:\s*hidden[^}]*background:\s*var\(--easyshare-list-surface\)/s,
|
||||
)
|
||||
})
|
||||
})
|
||||
File diff suppressed because one or more lines are too long
@@ -8,17 +8,23 @@ import {
|
||||
reorderDirectionFromKeyboard,
|
||||
type ReorderDirection,
|
||||
} from '@/utils/keyboard'
|
||||
import {
|
||||
springboardPageDragCompensation,
|
||||
springboardSwipeIntent,
|
||||
} from '@/utils/springboardDrag'
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
apps: PhoneAppDefinition[]
|
||||
defaultName: string
|
||||
editMode?: boolean
|
||||
externalDragVisual?: boolean
|
||||
folder: HomeFolder
|
||||
showLabel?: boolean
|
||||
}>(),
|
||||
{
|
||||
editMode: false,
|
||||
externalDragVisual: false,
|
||||
showLabel: true,
|
||||
},
|
||||
)
|
||||
@@ -45,10 +51,16 @@ let pointerId: number | null = null
|
||||
|
||||
const folderName = computed(() => props.folder.name || props.defaultName)
|
||||
const dragStyle = computed(() =>
|
||||
isDragging.value
|
||||
isDragging.value && !props.externalDragVisual
|
||||
? {
|
||||
transform: `translateX(${(phone.currentPage - dragStartPage) * dragPageWidth}px)`,
|
||||
translate: `${dragOffset.value.x}px ${dragOffset.value.y}px`,
|
||||
transform: `translate3d(${springboardPageDragCompensation(dragStartPage, phone.currentPage, dragPageWidth)}px, 0, 0)`,
|
||||
}
|
||||
: undefined,
|
||||
)
|
||||
const dragPointerStyle = computed(() =>
|
||||
isDragging.value && !props.externalDragVisual
|
||||
? {
|
||||
transform: `translate3d(${dragOffset.value.x}px, ${dragOffset.value.y}px, 0)`,
|
||||
}
|
||||
: undefined,
|
||||
)
|
||||
@@ -82,6 +94,7 @@ function beginPointerDrag(event: PointerEvent): void {
|
||||
|
||||
function onPointerDown(event: PointerEvent): void {
|
||||
if (event.button !== 0) return
|
||||
suppressClick.value = false
|
||||
pointerTarget = event.currentTarget as HTMLElement
|
||||
pointerId = event.pointerId
|
||||
pointerTarget.setPointerCapture(pointerId)
|
||||
@@ -109,9 +122,12 @@ function onPointerMove(event: PointerEvent): void {
|
||||
return
|
||||
}
|
||||
if (
|
||||
Math.hypot(event.clientX - pointerStart.x, event.clientY - pointerStart.y) >
|
||||
8
|
||||
springboardSwipeIntent(
|
||||
event.clientX - pointerStart.x,
|
||||
event.clientY - pointerStart.y,
|
||||
) !== 'pending'
|
||||
) {
|
||||
suppressClick.value = true
|
||||
clearHold()
|
||||
}
|
||||
}
|
||||
@@ -120,9 +136,11 @@ function onPointerUp(event: PointerEvent): void {
|
||||
clearHold()
|
||||
if (isDragging.value) {
|
||||
suppressClick.value = true
|
||||
emit('dragend', event)
|
||||
isDragging.value = false
|
||||
dragOffset.value = { x: 0, y: 0 }
|
||||
releasePointerCapture()
|
||||
emit('dragend', event)
|
||||
return
|
||||
}
|
||||
releasePointerCapture()
|
||||
}
|
||||
@@ -155,7 +173,7 @@ function onKeydown(event: KeyboardEvent): void {
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearHold()
|
||||
releasePointerCapture()
|
||||
cancelPointerDrag()
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -163,6 +181,7 @@ onBeforeUnmount(() => {
|
||||
<div
|
||||
class="home-folder-item app-icon-item"
|
||||
:class="{
|
||||
'app-icon-item--drag-source': isDragging && externalDragVisual,
|
||||
'app-icon-item--dragging': isDragging,
|
||||
'app-icon-item--editing': editMode,
|
||||
'home-folder-item--dragging': isDragging,
|
||||
@@ -176,6 +195,7 @@ onBeforeUnmount(() => {
|
||||
:aria-keyshortcuts="
|
||||
editMode ? 'ArrowLeft ArrowRight ArrowUp ArrowDown' : undefined
|
||||
"
|
||||
:style="dragPointerStyle"
|
||||
@click="openFolder"
|
||||
@contextmenu.prevent
|
||||
@keydown="onKeydown"
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const source = readFileSync(
|
||||
new URL('./MessageContactBubble.vue', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
|
||||
describe('MessageContactBubble Sky UI contract', () => {
|
||||
it('uses Sky buttons without direct Konsta markup', () => {
|
||||
expect(source).not.toContain("from 'konsta/vue'")
|
||||
expect(source).not.toMatch(/<\/?k-[a-z]/)
|
||||
expect(source).toContain('<SkyButton')
|
||||
})
|
||||
|
||||
it('inherits light and dark colors from Sky theme tokens', () => {
|
||||
expect(source).toContain('var(--sky-text)')
|
||||
expect(source).toContain('var(--sky-surface)')
|
||||
expect(source).toContain('var(--sky-surface-muted)')
|
||||
expect(source).toContain('var(--sky-muted)')
|
||||
expect(source).toContain('var(--sky-hairline)')
|
||||
expect(source).not.toContain(':global(.phone-app.dark)')
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,4 @@
|
||||
<script setup lang="ts">
|
||||
import { kButton } from 'konsta/vue'
|
||||
import {
|
||||
Check,
|
||||
ChevronRight,
|
||||
@@ -10,6 +9,7 @@ import {
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import type { SmsSharedContact } from '@/types/messages'
|
||||
import { SkyButton } from '@/ui'
|
||||
|
||||
const props = defineProps<{
|
||||
addLabel: string
|
||||
@@ -61,12 +61,13 @@ const initials = computed(() =>
|
||||
<ChevronRight :size="20" class="message-contact-card__chevron" />
|
||||
</div>
|
||||
<div class="message-contact-card__actions">
|
||||
<k-button rounded tonal @click.stop="emit('message')">
|
||||
<SkyButton rounded small tonal @click.stop="emit('message')">
|
||||
<MessageCircle :size="17" />
|
||||
{{ messageLabel }}
|
||||
</k-button>
|
||||
<k-button
|
||||
</SkyButton>
|
||||
<SkyButton
|
||||
rounded
|
||||
small
|
||||
tonal
|
||||
:disabled="saved"
|
||||
@click.stop="emit('save')"
|
||||
@@ -74,7 +75,7 @@ const initials = computed(() =>
|
||||
<Check v-if="saved" :size="17" />
|
||||
<UserPlus v-else :size="17" />
|
||||
{{ saved ? savedLabel : addLabel }}
|
||||
</k-button>
|
||||
</SkyButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -84,11 +85,15 @@ const initials = computed(() =>
|
||||
width: min(258px, 100%);
|
||||
width: min(258px, 72cqw);
|
||||
overflow: hidden;
|
||||
border: 1px solid rgb(255 255 255 / 48%);
|
||||
border: 1px solid var(--sky-hairline);
|
||||
border-radius: 21px;
|
||||
color: #151517;
|
||||
background: linear-gradient(155deg, rgb(255 255 255 / 96%), #edf5ff);
|
||||
box-shadow: 0 8px 24px rgb(22 63 112 / 14%);
|
||||
color: var(--sky-text);
|
||||
background: linear-gradient(
|
||||
155deg,
|
||||
var(--sky-surface-tint),
|
||||
var(--sky-surface)
|
||||
);
|
||||
box-shadow: 0 8px 24px rgb(0 0 0 / 16%);
|
||||
}
|
||||
|
||||
.message-contact-card__identity {
|
||||
@@ -107,9 +112,9 @@ const initials = computed(() =>
|
||||
border-radius: 50%;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
color: white;
|
||||
background: linear-gradient(145deg, #64d2ff, #0a84ff);
|
||||
box-shadow: 0 4px 14px rgb(10 132 255 / 24%);
|
||||
color: #ffffff;
|
||||
background: linear-gradient(145deg, var(--sky-success), #248a3d);
|
||||
box-shadow: 0 4px 14px rgb(52 199 89 / 24%);
|
||||
}
|
||||
|
||||
.message-contact-card__avatar img {
|
||||
@@ -143,23 +148,23 @@ const initials = computed(() =>
|
||||
|
||||
.message-contact-card__identity small {
|
||||
margin-top: 3px;
|
||||
color: #6e6e73;
|
||||
color: var(--sky-muted);
|
||||
font-size: 11.5px;
|
||||
}
|
||||
|
||||
.message-contact-card__chevron {
|
||||
color: #8e8e93;
|
||||
color: var(--sky-muted);
|
||||
}
|
||||
|
||||
.message-contact-card__actions {
|
||||
display: grid;
|
||||
gap: 1px;
|
||||
padding: 8px;
|
||||
border-top: 1px solid rgb(60 60 67 / 12%);
|
||||
background: rgb(255 255 255 / 58%);
|
||||
border-top: 1px solid var(--sky-hairline);
|
||||
background: var(--sky-surface-muted);
|
||||
}
|
||||
|
||||
.message-contact-card__actions :deep(.button) {
|
||||
.message-contact-card__actions :deep(.sky-button) {
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
min-height: 36px;
|
||||
@@ -167,20 +172,4 @@ const initials = computed(() =>
|
||||
padding-inline: 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
:global(.phone-app.dark) .message-contact-card {
|
||||
color: #f7f7f7;
|
||||
border-color: rgb(255 255 255 / 10%);
|
||||
background: linear-gradient(155deg, #34363b, #242a33);
|
||||
box-shadow: 0 8px 24px rgb(0 0 0 / 24%);
|
||||
}
|
||||
|
||||
:global(.phone-app.dark) .message-contact-card__identity small {
|
||||
color: #aeaeb2;
|
||||
}
|
||||
|
||||
:global(.phone-app.dark) .message-contact-card__actions {
|
||||
border-top-color: rgb(255 255 255 / 10%);
|
||||
background: rgb(0 0 0 / 10%);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const styles = readFileSync(
|
||||
new URL('../assets/main.css', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
|
||||
describe('PhoneLockScreen notification theme contract', () => {
|
||||
it('uses explicit light and dark notification material tokens', () => {
|
||||
expect(styles).toMatch(
|
||||
/\.lock-screen\s*\{[^}]*--lock-notification-background:\s*rgb\(38 36 40 \/ 76%\)[^}]*--lock-notification-color:\s*#fff/s,
|
||||
)
|
||||
expect(styles).toMatch(
|
||||
/\.phone-app--light \.lock-screen\s*\{[^}]*--lock-notification-background:\s*rgb\(247 247 248 \/ 82%\)[^}]*--lock-notification-color:\s*#111[^}]*--lock-notification-muted:\s*rgb\(0 0 0 \/ 55%\)/s,
|
||||
)
|
||||
})
|
||||
|
||||
it('applies the adaptive tokens to cards, metadata, focus and clear all', () => {
|
||||
expect(styles).toMatch(
|
||||
/\.lock-screen__notification\s*\{[^}]*color:\s*var\(--lock-notification-color\)[^}]*background-color:\s*var\(--lock-notification-background\)[^}]*text-shadow:\s*var\(--lock-notification-text-shadow\)/s,
|
||||
)
|
||||
expect(styles).toMatch(
|
||||
/\.lock-screen__notifications-clear\s*\{[^}]*color:\s*var\(--lock-notification-color\)[^}]*background:\s*var\(--lock-notification-clear-background\)/s,
|
||||
)
|
||||
expect(styles).toContain('color: var(--lock-notification-muted);')
|
||||
expect(styles).toContain(
|
||||
'outline: 2px solid var(--lock-notification-focus);',
|
||||
)
|
||||
})
|
||||
|
||||
it('uses the solid mode material for the performance clear action', () => {
|
||||
expect(styles).toMatch(
|
||||
/\.phone-app--performance \.lock-screen\s*\{[^}]*--lock-notification-clear-background:\s*var\(--phone-performance-glass\)/s,
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -73,9 +73,15 @@ const time = computed(() =>
|
||||
.join('')
|
||||
.trim(),
|
||||
)
|
||||
const dragStyle = computed(() => ({
|
||||
'--lock-drag': `${dragOffset.value}px`,
|
||||
}))
|
||||
const dragStyle = computed<Record<string, string>>(() => {
|
||||
const imageUrl = preferences.value.settings.wallpaperImageUrl
|
||||
return {
|
||||
'--lock-drag': `${dragOffset.value}px`,
|
||||
...(preferences.value.settings.wallpaper === 'custom' && imageUrl
|
||||
? { '--phone-wallpaper-image': `url(${JSON.stringify(imageUrl)})` }
|
||||
: {}),
|
||||
}
|
||||
})
|
||||
const flashlightShortcutColors = computed(() =>
|
||||
flashlightActive.value
|
||||
? {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const source = readFileSync(
|
||||
new URL('./PhoneSetupAssistant.vue', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
|
||||
describe('PhoneSetupAssistant contract', () => {
|
||||
it('uses first-party controls and exposes the complete setup journey', () => {
|
||||
expect(source).not.toContain("from 'konsta/vue'")
|
||||
expect(source).toContain('PhonePasscode')
|
||||
expect(source).toContain("step === 1")
|
||||
expect(source).toContain("step === 8")
|
||||
expect(source).toContain("['performance', 'ultimate']")
|
||||
expect(source).toContain('WALLPAPER_IDS')
|
||||
expect(source).toContain('setAllAppNotifications')
|
||||
expect(source).toContain('appStore.claimApp')
|
||||
expect(source).toContain('phone.completeSetup()')
|
||||
})
|
||||
|
||||
it('persists progress and supports resuming or moving backward', () => {
|
||||
expect(source).toContain('phone.preferences.settings.setupStep')
|
||||
expect(source).toContain('phone.setSetupStep(step.value)')
|
||||
expect(source).toContain('@click="moveTo(step - 1)"')
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,11 +9,13 @@ const props = withDefaults(
|
||||
defineProps<{
|
||||
activeCallReturn?: boolean
|
||||
controlCenterOpened?: boolean
|
||||
interactive?: boolean
|
||||
lockable?: boolean
|
||||
}>(),
|
||||
{
|
||||
activeCallReturn: false,
|
||||
controlCenterOpened: false,
|
||||
interactive: true,
|
||||
lockable: false,
|
||||
},
|
||||
)
|
||||
@@ -30,6 +32,7 @@ function updateTime(): void {
|
||||
}
|
||||
|
||||
function handleTimeClick(): void {
|
||||
if (!props.interactive) return
|
||||
if (props.activeCallReturn) {
|
||||
emit('activeCall')
|
||||
return
|
||||
@@ -76,7 +79,8 @@ onBeforeUnmount(() => {
|
||||
type="button"
|
||||
:aria-label="phone.t('ControlCenter.open')"
|
||||
:aria-expanded="controlCenterOpened"
|
||||
@click.stop="emit('controlCenter')"
|
||||
:disabled="!interactive"
|
||||
@click.stop="interactive && emit('controlCenter')"
|
||||
>
|
||||
<PhoneStatusIndicators
|
||||
:airplane-mode="phone.preferences.settings.airplaneMode"
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const source = readFileSync(
|
||||
new URL('./SpringboardWidget.vue', import.meta.url),
|
||||
'utf8',
|
||||
)
|
||||
|
||||
describe('SpringboardWidget UI contract', () => {
|
||||
it('uses the Sky widget frame without a Konsta surface', () => {
|
||||
expect(source).toContain('<SkyWidgetFrame')
|
||||
expect(source).not.toContain("from 'konsta/vue'")
|
||||
expect(source).not.toContain('<k-glass')
|
||||
expect(source).not.toContain('<k-badge')
|
||||
})
|
||||
|
||||
it('keeps the full widget surface clickable without affecting layout', () => {
|
||||
expect(source).toContain('class="home-widget-open"')
|
||||
expect(source).toContain('@click.stop="openWidget"')
|
||||
expect(source).toContain('position: absolute;')
|
||||
expect(source).toContain('background: transparent;')
|
||||
})
|
||||
|
||||
it('shows the reactive clock time in the calendar header', () => {
|
||||
expect(source).toContain('class="widget-date-time"')
|
||||
expect(source).toContain('{{ clock.time.value }}')
|
||||
expect(source).toContain('font-variant-numeric: tabular-nums;')
|
||||
})
|
||||
|
||||
it('reuses weather artwork and music metadata from the owning apps', () => {
|
||||
expect(source).toContain('<WeatherConditionIcon')
|
||||
expect(source).toContain('music.current.value?.artwork')
|
||||
expect(source).toContain('music.progress.value')
|
||||
})
|
||||
|
||||
it.each([
|
||||
'sunny',
|
||||
'clear',
|
||||
'partly_cloudy',
|
||||
'cloudy',
|
||||
'rain',
|
||||
'thunder',
|
||||
'fog',
|
||||
'snow',
|
||||
])('provides an adaptive %s weather surface', (condition) => {
|
||||
expect(source).toContain(`data-weather-condition='${condition}'`)
|
||||
})
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user