import { readFileSync } from 'node:fs' import { describe, expect, it } from 'vitest' const source = readFileSync( new URL('./MessagesApp.vue', import.meta.url), 'utf8', ) const styles = readFileSync( new URL('../../assets/main.css', import.meta.url), 'utf8', ) describe('MessagesApp Sky UI contract', () => { it('uses first-party Sky UI without direct Konsta markup', () => { expect(source).not.toContain("from 'konsta/vue'") expect(source).not.toMatch(/<\/?k-[a-z]/) expect(source).toContain(' { const inboxStart = source.indexOf( 'class="messages-sky-page messages-sky-inbox"', ) const composeStart = source.indexOf('v-else-if="composing"') const inbox = source.slice(inboxStart, composeStart) expect(source).toContain("id: 'sort-newest'") expect(source).toContain("id: 'sort-oldest'") expect(source).toContain("id: 'filter-all'") expect(source).toContain("id: 'filter-unread'") expect(source).toContain("id: 'edit'") expect(source).toContain("phone.t('Apps.messages.sortNewest')") expect(source).toContain("phone.t('Apps.messages.sortOldest')") expect(source).toContain("phone.t('Apps.messages.allMessages')") expect(source).toContain("phone.t('Apps.messages.unreadMessages')") expect(source).toContain("phone.t('Common.edit')") expect(source).toContain("group: 'sort'") expect(source).toContain("group: 'filter'") expect(source).toContain("phone.t('Apps.messages.sortLabel')") expect(source).toContain("phone.t('Apps.messages.filterLabel')") expect(source).toContain( 'inboxMenuTarget.value = event.currentTarget as HTMLElement', ) expect(source).toContain("if (id === 'edit') toggleListEditing()") expect(inbox).toContain('class="messages-sky-inbox-menu-trigger"') expect(inbox).toContain('aria-haspopup="menu"') expect(inbox).toContain(':aria-expanded="inboxMenuOpened"') expect(inbox).toContain('@click="openInboxMenu"') expect(inbox).toContain('id="messages-inbox-menu"') expect(inbox).toContain(':items="inboxMenuItems"') expect(inbox).toContain(':opened="inboxMenuOpened"') expect(inbox).toContain(':target="inboxMenuTarget"') expect(inbox).toContain('@backdropclick="dismissInboxMenu"') expect(inbox).toContain('@escape="dismissInboxMenu"') expect(inbox).toContain('@positionerror="dismissInboxMenu"') expect(inbox).toContain('@select="selectInboxMenuItem"') }) it('places search and the compose action together in the bottom toolbar', () => { const inboxStart = source.indexOf( 'class="messages-sky-page messages-sky-inbox"', ) const composeStart = source.indexOf('v-else-if="composing"') const inbox = source.slice(inboxStart, composeStart) const scrollEnd = inbox.indexOf('') const toolbarStart = inbox.indexOf('', toolbarStart) const toolbar = inbox.slice(toolbarStart, toolbarEnd) const searchbarStart = toolbar.indexOf('') expect(inbox).not.toContain('messages-sky-compose-navigation') }) it('uses the results-empty state for search and unread filters', () => { expect(source).toContain('search || showUnreadOnly') expect(source).toContain('v-if="!search && !showUnreadOnly" #actions') }) it('keeps the unread marker in the media slot left of the avatar', () => { const inboxStart = source.indexOf( 'class="messages-sky-page messages-sky-inbox"', ) const composeStart = source.indexOf('v-else-if="composing"') const inbox = source.slice(inboxStart, composeStart) const mediaSlotStart = inbox.indexOf('