fix: complete acronym casing follow-ups
- Cover UI identifiers in the acronym guardrail - Preserve FIG thumbnail and metadata values through archive parsing - Use Vue-compatible acronym prop attributes
This commit is contained in:
parent
7f91594d0c
commit
55b368dc4a
|
|
@ -2110,7 +2110,7 @@ const noMixedCaseAcronymIdentifiers = {
|
|||
}
|
||||
},
|
||||
create(context) {
|
||||
const canonicalAcronym = /(?:Acp|Ai|Api|Cli|Cors|Css|Html|Ime|Json|Jsx|Mcp|Pdf|Png|Rgb|Rpc|Rtl|Svg|Url|Uri|Xml)/g
|
||||
const canonicalAcronym = /(?:Acp|Ai|Api|Cli|Cors|Css|Html|Ime|Json|Jsx|Mcp|Pdf|Png|Rgb|Rpc|Rtl|Svg|Ui|Url|Uri|Xml)/g
|
||||
const ignoredImports = new Set([
|
||||
'@agentclientprotocol/sdk',
|
||||
'@tauri-apps/plugin-clipboard-manager',
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ export interface FigParseResult {
|
|||
figKiwiVersion: number
|
||||
/** Deflated Kiwi schema bytes from the original file, retained for round-trip fidelity. */
|
||||
figSchemaDeflated: Uint8Array
|
||||
thumbnailPNG: Uint8Array | null
|
||||
metaJSON: string | null
|
||||
}
|
||||
|
||||
function isLikelyAsset(name: string): boolean {
|
||||
|
|
@ -46,12 +48,7 @@ function findCanvasData(entries: Partial<Record<string, Uint8Array>>): Uint8Arra
|
|||
|
||||
/** Parse a complete zipped `.fig` file into its Figma protocol payload and binary resources. */
|
||||
export function parseFigBuffer(buffer: ArrayBuffer): FigParseResult {
|
||||
const archive = unzipSync(new Uint8Array(buffer), {
|
||||
filter: (file) =>
|
||||
file.name === 'canvas.fig' ||
|
||||
file.name === 'canvas' ||
|
||||
(file.name.startsWith('images/') && file.name !== 'images/')
|
||||
})
|
||||
const archive = unzipSync(new Uint8Array(buffer))
|
||||
const canvasData = findCanvasData(archive)
|
||||
if (!canvasData) {
|
||||
throw new Error(
|
||||
|
|
@ -60,11 +57,17 @@ export function parseFigBuffer(buffer: ArrayBuffer): FigParseResult {
|
|||
}
|
||||
|
||||
const decoded = decodeFigKiwiCanvas(canvasData)
|
||||
const metaBytes = archive['meta.json']
|
||||
const images = Object.entries(archive)
|
||||
.filter(([name]) => name.startsWith('images/') && name !== 'images/')
|
||||
.map(([name, data]) => [name.slice('images/'.length), data] as [string, Uint8Array])
|
||||
|
||||
return { ...decoded, images }
|
||||
return {
|
||||
...decoded,
|
||||
images,
|
||||
thumbnailPNG: archive['thumbnail.png'] ?? null,
|
||||
metaJSON: Object.hasOwn(archive, 'meta.json') ? new TextDecoder().decode(metaBytes) : null
|
||||
}
|
||||
}
|
||||
|
||||
/** Assemble a complete zipped `.fig` archive from an encoded Kiwi message and resources. */
|
||||
|
|
|
|||
|
|
@ -41,6 +41,8 @@ describe('@open-pencil/fig package API', () => {
|
|||
})
|
||||
|
||||
it('parses complete .fig archives and image resources', () => {
|
||||
const thumbnailPNG = new Uint8Array([1])
|
||||
const metaJSON = '{}'
|
||||
const bytes = writeFigArchive({
|
||||
schemaDeflated: deflateSync(getSchemaBytes()),
|
||||
kiwiData: encodeMessage(
|
||||
|
|
@ -53,8 +55,8 @@ describe('@open-pencil/fig package API', () => {
|
|||
}
|
||||
])
|
||||
),
|
||||
thumbnailPNG: new Uint8Array([1]),
|
||||
metaJSON: '{}',
|
||||
thumbnailPNG,
|
||||
metaJSON,
|
||||
images: [{ name: 'images/hash', data: new Uint8Array([9, 8, 7]) }]
|
||||
})
|
||||
const parsed = parseFigBuffer(bytes.buffer as ArrayBuffer)
|
||||
|
|
@ -62,6 +64,8 @@ describe('@open-pencil/fig package API', () => {
|
|||
expect(parsed.nodeChanges).toHaveLength(1)
|
||||
expect(parsed.nodeChanges[0]?.type).toBe('DOCUMENT')
|
||||
expect(parsed.images).toEqual([['hash', new Uint8Array([9, 8, 7])]])
|
||||
expect(parsed.thumbnailPNG).toEqual(thumbnailPNG)
|
||||
expect(parsed.metaJSON).toBe(metaJSON)
|
||||
})
|
||||
|
||||
it('rejects invalid fig-kiwi containers', () => {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { evictLocalFigCache } from '@/app/storage/cache-eviction'
|
|||
import { getLocalCanvasStore } from '@/app/storage/local-store'
|
||||
import { getOutbox } from '@/app/storage/sync/outbox'
|
||||
import { setUploadProgress } from '@/app/storage/sync/progress'
|
||||
import { setPendingSyncCount, setSyncUi } from '@/app/storage/sync/status'
|
||||
import { setPendingSyncCount, setSyncUI } from '@/app/storage/sync/status'
|
||||
import type { OutboxJob } from '@/app/storage/sync/types'
|
||||
|
||||
const MAX_ATTEMPTS = 8
|
||||
|
|
@ -141,17 +141,17 @@ async function pumpOnce(): Promise<void> {
|
|||
setPendingSyncCount(jobs.length)
|
||||
|
||||
if (jobs.length === 0) {
|
||||
if (isOnline()) setSyncUi('idle')
|
||||
if (isOnline()) setSyncUI('idle')
|
||||
return
|
||||
}
|
||||
|
||||
if (!isOnline()) {
|
||||
setSyncUi('offline')
|
||||
setSyncUI('offline')
|
||||
scheduleWake(5000)
|
||||
return
|
||||
}
|
||||
|
||||
setSyncUi('syncing')
|
||||
setSyncUI('syncing')
|
||||
const now = Date.now()
|
||||
// Single-flight globally for simplicity (large figs)
|
||||
const job = jobs.find((j) => j.nextAttemptAt <= now)
|
||||
|
|
@ -166,7 +166,7 @@ async function pumpOnce(): Promise<void> {
|
|||
await outbox.remove(job.id)
|
||||
const remaining = await outbox.list()
|
||||
setPendingSyncCount(remaining.length)
|
||||
if (remaining.length === 0) setSyncUi('idle')
|
||||
if (remaining.length === 0) setSyncUI('idle')
|
||||
else scheduleWake(50)
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error)
|
||||
|
|
@ -175,7 +175,7 @@ async function pumpOnce(): Promise<void> {
|
|||
...job,
|
||||
nextAttemptAt: Number.MAX_SAFE_INTEGER
|
||||
})
|
||||
setSyncUi('error', message)
|
||||
setSyncUI('error', message)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -191,7 +191,7 @@ async function pumpOnce(): Promise<void> {
|
|||
syncStatus: 'error',
|
||||
lastSyncError: message
|
||||
})
|
||||
setSyncUi('error', message.slice(0, 120))
|
||||
setSyncUI('error', message.slice(0, 120))
|
||||
} else {
|
||||
// Keep a record without touching syncStatus so the stale remote
|
||||
// thumbnail is at least diagnosable.
|
||||
|
|
@ -202,7 +202,7 @@ async function pumpOnce(): Promise<void> {
|
|||
const remaining = await outbox.list()
|
||||
setPendingSyncCount(remaining.length)
|
||||
if (remaining.length > 0) scheduleWake(1000)
|
||||
else setSyncUi('idle')
|
||||
else setSyncUI('idle')
|
||||
} else {
|
||||
// Never discard a document mutation. Keep it durable until the user
|
||||
// repairs credentials/permissions and explicitly wakes synchronization.
|
||||
|
|
@ -247,11 +247,11 @@ function ensureOnlineListeners() {
|
|||
if (onlineBound || !IS_BROWSER) return
|
||||
onlineBound = true
|
||||
window.addEventListener('online', () => {
|
||||
setSyncUi('syncing')
|
||||
setSyncUI('syncing')
|
||||
void kickSyncEngine()
|
||||
})
|
||||
window.addEventListener('offline', () => {
|
||||
setSyncUi('offline')
|
||||
setSyncUI('offline')
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -307,7 +307,7 @@ export async function resumeStorageSync(): Promise<void> {
|
|||
const jobs = await outbox.list()
|
||||
const now = Date.now()
|
||||
await Promise.all(jobs.map((job) => outbox.update({ ...job, nextAttemptAt: now })))
|
||||
if (jobs.length > 0) setSyncUi('syncing')
|
||||
if (jobs.length > 0) setSyncUI('syncing')
|
||||
void kickSyncEngine()
|
||||
}
|
||||
|
||||
|
|
@ -316,5 +316,5 @@ export async function clearStorageLocalMirror(): Promise<void> {
|
|||
await getLocalCanvasStore().clearAll()
|
||||
await getOutbox().clear()
|
||||
setPendingSyncCount(0)
|
||||
setSyncUi('idle')
|
||||
setSyncUI('idle')
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,15 +17,15 @@ export { setUploadProgress, uploadProgressByCanvas } from './progress'
|
|||
export {
|
||||
pendingSyncCount,
|
||||
setPendingSyncCount,
|
||||
setSyncUi,
|
||||
setSyncUI,
|
||||
syncStatusLabel,
|
||||
syncUiDetail,
|
||||
syncUiState
|
||||
syncUIDetail,
|
||||
syncUIState
|
||||
} from './status'
|
||||
export {
|
||||
makeJobId,
|
||||
supersedePutCanvasJobs,
|
||||
type OutboxJob,
|
||||
type OutboxJobType,
|
||||
type SyncUiState
|
||||
type SyncUIState
|
||||
} from './types'
|
||||
|
|
|
|||
|
|
@ -1,28 +1,28 @@
|
|||
import { computed, ref } from 'vue'
|
||||
|
||||
import type { SyncUiState } from '@/app/storage/sync/types'
|
||||
import type { SyncUIState } from '@/app/storage/sync/types'
|
||||
|
||||
/** Global subtle sync status for UI chips. */
|
||||
export const syncUiState = ref<SyncUiState>('idle')
|
||||
export const syncUiDetail = ref<string | null>(null)
|
||||
export const syncUIState = ref<SyncUIState>('idle')
|
||||
export const syncUIDetail = ref<string | null>(null)
|
||||
export const pendingSyncCount = ref(0)
|
||||
|
||||
export const syncStatusLabel = computed(() => {
|
||||
switch (syncUiState.value) {
|
||||
switch (syncUIState.value) {
|
||||
case 'syncing':
|
||||
return syncUiDetail.value ?? 'Syncing…'
|
||||
return syncUIDetail.value ?? 'Syncing…'
|
||||
case 'offline':
|
||||
return 'Offline · will sync'
|
||||
case 'error':
|
||||
return syncUiDetail.value ?? 'Sync failed'
|
||||
return syncUIDetail.value ?? 'Sync failed'
|
||||
default:
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
export function setSyncUi(state: SyncUiState, detail: string | null = null) {
|
||||
syncUiState.value = state
|
||||
syncUiDetail.value = detail
|
||||
export function setSyncUI(state: SyncUIState, detail: string | null = null) {
|
||||
syncUIState.value = state
|
||||
syncUIDetail.value = detail
|
||||
}
|
||||
|
||||
export function setPendingSyncCount(count: number) {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export type OutboxJob = {
|
|||
nextAttemptAt: number
|
||||
}
|
||||
|
||||
export type SyncUiState = 'idle' | 'syncing' | 'offline' | 'error'
|
||||
export type SyncUIState = 'idle' | 'syncing' | 'offline' | 'error'
|
||||
|
||||
/** Pure helper: drop older putCanvas jobs for same canvas when a newer revision is enqueued. */
|
||||
export function supersedePutCanvasJobs(
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ const toolShortcuts: Record<Tool, string> = {
|
|||
}
|
||||
|
||||
const flyoutMenuCls = useMenuUI({ content: 'min-w-32' })
|
||||
const toolbarUi = { flyoutContent: flyoutMenuCls.content }
|
||||
const toolbarUI = { flyoutContent: flyoutMenuCls.content }
|
||||
const { editActions, arrangeActions } = useToolbarActions({ store, getCommand, menu })
|
||||
|
||||
const { mobileCategory, slideDirection, hasPrev, hasNext, goPrev, goNext } = useToolbarState()
|
||||
|
|
@ -75,7 +75,7 @@ function onActionTap(item: ToolbarActionItem) {
|
|||
:tool-icons="toolIcons"
|
||||
:tool-labels="toolLabels"
|
||||
:tool-shortcuts="toolShortcuts"
|
||||
:ui="toolbarUi"
|
||||
:ui="toolbarUI"
|
||||
@set-tool="actions.setTool"
|
||||
/>
|
||||
|
||||
|
|
@ -87,7 +87,7 @@ function onActionTap(item: ToolbarActionItem) {
|
|||
:tool-icons="toolIcons"
|
||||
:tool-labels="toolLabels"
|
||||
:tool-shortcuts="toolShortcuts"
|
||||
:ui="toolbarUi"
|
||||
:ui="toolbarUI"
|
||||
:mobile-category="mobileCategory"
|
||||
:slide-direction="slideDirection"
|
||||
:has-prev="hasPrev"
|
||||
|
|
|
|||
|
|
@ -376,8 +376,8 @@ void refreshKeyStatus()
|
|||
:saved="hasExistingKey"
|
||||
kind="api"
|
||||
:placeholder="hasExistingKey ? dialogs.keySavedReplace : providerDef.keyPlaceholder"
|
||||
:key-url="providerDef.keyURL"
|
||||
:key-url-label="dialogs.getAPIKeyGeneric"
|
||||
:key-u-r-l="providerDef.keyURL"
|
||||
:key-u-r-l-label="dialogs.getAPIKeyGeneric"
|
||||
@clear="clearKey"
|
||||
/>
|
||||
|
||||
|
|
|
|||
|
|
@ -44,8 +44,8 @@ async function clearUnsplashKey(): Promise<void> {
|
|||
:saved="hasExistingPexelsKey"
|
||||
kind="pexels"
|
||||
:placeholder="hasExistingPexelsKey ? dialogs.keySavedReplace : dialogs.stockPhotoToolOptional"
|
||||
key-url="https://www.pexels.com/api/"
|
||||
:key-url-label="dialogs.getPexelsAPIKey"
|
||||
key-u-r-l="https://www.pexels.com/api/"
|
||||
:key-u-r-l-label="dialogs.getPexelsAPIKey"
|
||||
@clear="clearPexelsKey"
|
||||
@change="savePexelsKey"
|
||||
/>
|
||||
|
|
@ -58,8 +58,8 @@ async function clearUnsplashKey(): Promise<void> {
|
|||
:placeholder="
|
||||
hasExistingUnsplashKey ? dialogs.keySavedReplace : dialogs.pexelsAlternativeOptional
|
||||
"
|
||||
key-url="https://unsplash.com/oauth/applications"
|
||||
:key-url-label="dialogs.getUnsplashAccessKey"
|
||||
key-u-r-l="https://unsplash.com/oauth/applications"
|
||||
:key-u-r-l-label="dialogs.getUnsplashAccessKey"
|
||||
@clear="clearUnsplashKey"
|
||||
@change="saveUnsplashKey"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ onMounted(() => void refreshStatus())
|
|||
:saved="keyStatus === 'configured'"
|
||||
kind="api"
|
||||
:placeholder="keyStatus === 'configured' ? dialogs.keySavedReplace : provider.keyPlaceholder"
|
||||
:key-url="provider.keyURL"
|
||||
:key-url-label="dialogs.getAPIKeyGeneric"
|
||||
:key-u-r-l="provider.keyURL"
|
||||
:key-u-r-l-label="dialogs.getAPIKeyGeneric"
|
||||
@change="saveCredential"
|
||||
@clear="clearCredential"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ heavy('parse heavy .fig files', () => {
|
|||
let material3: SceneGraph
|
||||
let nuxtui: SceneGraph
|
||||
let material3Nodes: SceneNode[]
|
||||
let nuxtUiNodes: SceneNode[]
|
||||
let nuxtUINodes: SceneNode[]
|
||||
|
||||
beforeAll(async () => {
|
||||
material3 = await parseFixture('material3.fig', { populate: 'none' })
|
||||
nuxtui = await parseFixture('nuxtui.fig', { populate: 'none' })
|
||||
material3Nodes = collectAllNodes(material3)
|
||||
nuxtUiNodes = collectAllNodes(nuxtui)
|
||||
nuxtUINodes = collectAllNodes(nuxtui)
|
||||
})
|
||||
|
||||
test('material3.fig parses with pages and nodes', () => {
|
||||
|
|
@ -30,7 +30,7 @@ heavy('parse heavy .fig files', () => {
|
|||
test('nuxtui.fig parses with pages and nodes', () => {
|
||||
expect(nuxtui).toBeInstanceOf(SceneGraph)
|
||||
expect(nuxtui.getPages().length).toBeGreaterThan(0)
|
||||
expect(nuxtUiNodes.length).toBeGreaterThan(0)
|
||||
expect(nuxtUINodes.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
test('material3: contains COMPONENT nodes', () => {
|
||||
|
|
@ -43,7 +43,7 @@ heavy('parse heavy .fig files', () => {
|
|||
})
|
||||
|
||||
test('nuxtui: no unmapped node types', () => {
|
||||
const invalid = nuxtUiNodes.filter((n) => !VALID_NODE_TYPES.has(n.type))
|
||||
const invalid = nuxtUINodes.filter((n) => !VALID_NODE_TYPES.has(n.type))
|
||||
expect(invalid.map((n) => `${n.name}: ${n.type}`)).toEqual([])
|
||||
})
|
||||
|
||||
|
|
@ -66,7 +66,7 @@ heavy('parse heavy .fig files', () => {
|
|||
})
|
||||
|
||||
test('nuxtui: fills have valid colors', () => {
|
||||
for (const n of nuxtUiNodes) {
|
||||
for (const n of nuxtUINodes) {
|
||||
for (const fill of n.fills) {
|
||||
if (fill.type === 'SOLID') {
|
||||
expect(fill.color.r).toBeGreaterThanOrEqual(0)
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ export function dynamicClassDiagnostics(sourceRel: string, content: string) {
|
|||
return diagnostics
|
||||
}
|
||||
|
||||
function containsUiHookCall(node: unknown, visited = new Set<ExpressionNode>()): boolean {
|
||||
function containsUIHookCall(node: unknown, visited = new Set<ExpressionNode>()): boolean {
|
||||
if (!isExpressionNode(node) || visited.has(node)) return false
|
||||
visited.add(node)
|
||||
if (
|
||||
|
|
@ -127,8 +127,8 @@ function containsUiHookCall(node: unknown, visited = new Set<ExpressionNode>()):
|
|||
return Object.entries(node).some(([key, value]) => {
|
||||
if (key === 'loc' || key === 'start' || key === 'end') return false
|
||||
return Array.isArray(value)
|
||||
? value.some((child) => containsUiHookCall(child, visited))
|
||||
: containsUiHookCall(value, visited)
|
||||
? value.some((child) => containsUIHookCall(child, visited))
|
||||
: containsUIHookCall(value, visited)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -145,7 +145,7 @@ export function vueTemplateGuardrailDiagnostics(sourceRel: string, content: stri
|
|||
column: node.loc?.start?.column
|
||||
})
|
||||
}
|
||||
if (node.type === VUE_DIRECTIVE_NODE && containsUiHookCall(node.exp?.ast)) {
|
||||
if (node.type === VUE_DIRECTIVE_NODE && containsUIHookCall(node.exp?.ast)) {
|
||||
diagnostics.push({
|
||||
message: 'Resolve use*UI() hooks once in script setup, not inside template expressions.',
|
||||
line: node.loc?.start?.line,
|
||||
|
|
@ -156,7 +156,7 @@ export function vueTemplateGuardrailDiagnostics(sourceRel: string, content: stri
|
|||
return diagnostics
|
||||
}
|
||||
|
||||
export const noVueTemplateUiHooksOrSVG = createTextRule(
|
||||
export const noVueTemplateUIHooksOrSVG = createTextRule(
|
||||
'open-pencil/no-vue-template-ui-hooks-or-svg',
|
||||
vueTemplateGuardrailDiagnostics
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { parse as parseVueSfc } from 'vue/compiler-sfc'
|
|||
import { noCrossPackageReexportShims } from './cross-package-reexport-shims.ts'
|
||||
import {
|
||||
noDynamicTailwindStateClasses,
|
||||
noVueTemplateUiHooksOrSVG
|
||||
noVueTemplateUIHooksOrSVG
|
||||
} from './dynamic-tailwind-classes.ts'
|
||||
import {
|
||||
collectFolders,
|
||||
|
|
@ -305,7 +305,7 @@ const noComponentsImportViews = createImportRule(
|
|||
}
|
||||
)
|
||||
|
||||
const noNonUiImportsInSharedUi = createImportRule(
|
||||
const noNonUIImportsInSharedUI = createImportRule(
|
||||
'open-pencil/no-non-ui-imports-in-shared-ui',
|
||||
(sourceRel, _specifier, resolved) => {
|
||||
if (!sourceRel.startsWith('src/components/ui/')) return null
|
||||
|
|
@ -326,7 +326,7 @@ const noViewsImportedOutsideEntry = createImportRule(
|
|||
}
|
||||
)
|
||||
|
||||
const noAppImportsInSharedUi = createImportRule(
|
||||
const noAppImportsInSharedUI = createImportRule(
|
||||
'open-pencil/no-app-imports-in-shared-ui',
|
||||
(sourceRel, _specifier, resolved) => {
|
||||
if (!sourceRel.startsWith('src/components/ui/')) return null
|
||||
|
|
@ -476,7 +476,7 @@ const noShortcutTextInLabels = createTextRule(
|
|||
}
|
||||
)
|
||||
|
||||
const noUiImportsInCore = createImportRule(
|
||||
const noUIImportsInCore = createImportRule(
|
||||
'open-pencil/no-ui-imports-in-core',
|
||||
(sourceRel, specifier) => {
|
||||
if (!sourceRel.startsWith('packages/core/src/')) return null
|
||||
|
|
@ -514,15 +514,15 @@ export const openPencilArchitecturePlugin = {
|
|||
noAppImportsComponentsOrViews,
|
||||
noComponentsImportViews,
|
||||
noViewsImportedOutsideEntry,
|
||||
noNonUiImportsInSharedUi,
|
||||
noAppImportsInSharedUi,
|
||||
noNonUIImportsInSharedUI,
|
||||
noAppImportsInSharedUI,
|
||||
noPropertyPanelInternalsOutsidePanel,
|
||||
noProductionTestIdsInSharedLayers,
|
||||
noDynamicTailwindStateClasses,
|
||||
noVueTemplateUiHooksOrSVG,
|
||||
noVueTemplateUIHooksOrSVG,
|
||||
noNativeTitleAttributesInVue,
|
||||
noShortcutTextInLabels,
|
||||
noHardcodedMacOSShortcutGlyphs,
|
||||
noUiImportsInCore
|
||||
noUIImportsInCore
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,10 +35,13 @@ afterEach(() => {
|
|||
|
||||
describe('acronym identifier casing', () => {
|
||||
test('rejects mixed-case first-party acronym identifiers', () => {
|
||||
const result = lint('export class CloudCorsError {}\nexport function sendRpc() {}')
|
||||
const result = lint(
|
||||
'export class CloudCorsError {}\nexport function sendRpc() {}\nexport type FontPickerUi = {}'
|
||||
)
|
||||
expect(result.exitCode).toBe(1)
|
||||
expect(result.stdout.toString()).toContain('CloudCorsError')
|
||||
expect(result.stdout.toString()).toContain('sendRpc')
|
||||
expect(result.stdout.toString()).toContain('FontPickerUi')
|
||||
})
|
||||
|
||||
test('accepts canonical first-party acronym identifiers', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue