@moontra/moonui 6.28.0 → 6.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cdn/moonui.css +2 -2
- package/dist/cdn/moonui.esm.js +3 -3
- package/dist/cdn/moonui.global.js +3 -3
- package/dist/index.js +157 -16
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +157 -16
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/__tests__/issue-646-dark-scope-override.test.tsx +19 -2
- package/src/__tests__/issue-708-badge-outline-override.test.tsx +319 -0
- package/src/__tests__/issue-715-button-override-hover.test.tsx +631 -0
- package/src/components/ui/__tests__/select.test.tsx +13 -3
- package/src/components/ui/badge.tsx +25 -1
- package/src/components/ui/button.tsx +131 -14
- package/src/components/ui/select.tsx +1 -1
- package/src/styles/tokens.css +45 -1
package/package.json
CHANGED
|
@@ -266,8 +266,25 @@ describe('#646 — kaynak çapaları (regresyon koruması)', () => {
|
|
|
266
266
|
|
|
267
267
|
// ════════════════════════════════════════════════════════════════════════════════
|
|
268
268
|
describe('#646 — NEGATİF KONTROL: kapsam dışı yollar BOZULMADI', () => {
|
|
269
|
-
|
|
270
|
-
|
|
269
|
+
/**
|
|
270
|
+
* ⚠️ KAPSAM GÜNCELLENDİ (#708 kalem 3). Bu madde #646 gününde bir NEGATİF
|
|
271
|
+
* KONTROLDÜ: "`primary` düzeltilirken `outline`a dokunulmadı" iddiasını
|
|
272
|
+
* kilitliyordu. #708 `outline`ı BİLEREK kapsama aldı — docs "PRO" rozetinde
|
|
273
|
+
* `dark:text-gray-100` tüketicinin `text-white`ını ezip koyu temada 4.18:1
|
|
274
|
+
* (AA 4.5 altı) üretiyordu. `dark:bg-transparent` de saf tekrardı.
|
|
275
|
+
*
|
|
276
|
+
* Kontrolün AMACI korunuyor, yönü tersine çevrildi: `outline`ın #646'da
|
|
277
|
+
* dokunulmayan diğer yolları (öneksiz çift, kenarlık, ring, blur) hâlâ yerinde.
|
|
278
|
+
*/
|
|
279
|
+
it('Badge `outline` #708 ile kapsama alındı — `dark:bg-*`/`dark:text-*` yok', () => {
|
|
280
|
+
const cls = parcala(badgeVariants({ variant: 'outline' }))
|
|
281
|
+
expect(cls).not.toContain('dark:bg-transparent')
|
|
282
|
+
expect(cls.filter((c) => c.startsWith('dark:text-'))).toEqual([])
|
|
283
|
+
// #646'nın gerçek negatif kontrolü: geri kalan yollar bozulmadı
|
|
284
|
+
expect(cls).toContain('bg-transparent')
|
|
285
|
+
expect(cls).toContain('text-foreground')
|
|
286
|
+
expect(cls).toContain('dark:border-gray-600')
|
|
287
|
+
expect(cls).toContain('dark:backdrop-blur-sm')
|
|
271
288
|
})
|
|
272
289
|
|
|
273
290
|
it('Badge `destructive` #544 yolu bozulmadı (`bg-error-600`)', () => {
|
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #708 kalem 3 — docs "PRO" rozeti koyu temada AA altında (4.177:1).
|
|
3
|
+
*
|
|
4
|
+
* ══ Mekanizma — #715 Dal 1'in BİREBİR AYNISI, bu kez `badge`de ═══════════════════
|
|
5
|
+
* `cn()` = `twMerge(clsx(...))`. Tüketici (`DocsHeader.getBadgeClassName()`)
|
|
6
|
+
* `text-white` yazdığında twMerge ÖNEKSİZ `text-foreground`u eler — ama
|
|
7
|
+
* `dark:text-gray-100` AYRI bir modifier grubu olduğu için ELENMEZ ve koyu temada
|
|
8
|
+
* özgüllükle kazanır:
|
|
9
|
+
* .dark\:text-gray-100:is(.dark *) → (0,2,0)
|
|
10
|
+
* text-white → (0,1,0)
|
|
11
|
+
*
|
|
12
|
+
* Rozet zemini `from-brand-600 to-brand-accent-600` gradyanı; metin `text-xs`
|
|
13
|
+
* (küçük metin → AA 4.5:1). `--brand-*` token'larının `.dark` ezmesi YOK
|
|
14
|
+
* (tokens.css:93-106 yalnız `:root`ta) → zemin iki temada birebir aynı, yalnız
|
|
15
|
+
* METİN değişiyordu:
|
|
16
|
+
* light beyaz → en kötü uç 4.60 ✓
|
|
17
|
+
* dark gray-100 → en kötü uç 4.18 ✗ (A18-V 10/10 sayfada ölçtü)
|
|
18
|
+
*
|
|
19
|
+
* ══ #715'in ayrımı burada nasıl uygulandı ═══════════════════════════════════════
|
|
20
|
+
* "Her `dark:` satırını sil" YANLIŞ olurdu. Satır satır soruldu:
|
|
21
|
+
* · `dark:bg-transparent` → öneksiz `bg-transparent` ZATEN dizide (badge.tsx:53)
|
|
22
|
+
* ve `outline`da başka `bg-*` yok → sıralama guard'ı
|
|
23
|
+
* da DEĞİL → SAF TEKRAR → KALDIRILDI.
|
|
24
|
+
* · `dark:text-gray-100` → öneksiz ikizi `text-foreground`tan FARKLI bir değere
|
|
25
|
+
* gidiyor (aşağıda ölçüldü), yani #715'in "tekrar"
|
|
26
|
+
* tanımına girmiyor. Yine de KALDIRILDI, çünkü
|
|
27
|
+
* kazandırdığı fark AA'yı etkilemeyecek kadar küçük
|
|
28
|
+
* (aşağıdaki "kendi görünümü" testi ölçüyor) ama
|
|
29
|
+
* tüketicinin AA'yı geçen `text-white`ını EZİYOR.
|
|
30
|
+
* · `dark:backdrop-blur-sm` → öneksiz ikizi YOK, koyu-temaya özgü gerçek bir
|
|
31
|
+
* efekt → KORUNDU.
|
|
32
|
+
*
|
|
33
|
+
* ⚠️ SIRALAMA GUARD'I KONTROLÜ (#715'in ikinci dersi): guard yalnız varyant AYNI
|
|
34
|
+
* özellik için FARKLI değerli bir `dark:` taban kuralı da bildiriyorsa gerekir.
|
|
35
|
+
* `outline`da tek `bg-*` `bg-transparent`, tek `text-*` `text-foreground` →
|
|
36
|
+
* yenilecek rakip yok → guard gerekçesi yok. (Aşağıda kaynaktan doğrulanıyor.)
|
|
37
|
+
*
|
|
38
|
+
* ⚠️ jsdom Tailwind kaskadını çözmez → burada `getComputedStyle` ile RENK ölçülmez.
|
|
39
|
+
* Ölçülen şey MEKANİZMA: (a) gerçek `cn()`/twMerge ile hangi sınıflar sağ kalıyor,
|
|
40
|
+
* (b) sağ kalan sınıfların token değerleriyle WCAG oranı. Gerçek tarayıcı
|
|
41
|
+
* doğrulaması bu dosyanın kapsamı DIŞINDADIR ve raporda ayrıca belirtilir.
|
|
42
|
+
*/
|
|
43
|
+
import * as React from 'react'
|
|
44
|
+
import { render } from '@testing-library/react'
|
|
45
|
+
import '@testing-library/jest-dom'
|
|
46
|
+
import * as fs from 'fs'
|
|
47
|
+
import * as path from 'path'
|
|
48
|
+
import { Badge } from '../components/ui/badge'
|
|
49
|
+
import { cn } from '../lib/utils'
|
|
50
|
+
|
|
51
|
+
const PKG = path.resolve(__dirname, '..')
|
|
52
|
+
const DEPO = path.resolve(__dirname, '../../../..')
|
|
53
|
+
const tokens = fs.readFileSync(path.join(PKG, 'styles/tokens.css'), 'utf8')
|
|
54
|
+
const freeSrc = fs.readFileSync(path.join(PKG, 'components/ui/badge.tsx'), 'utf8')
|
|
55
|
+
const proSrc = fs.readFileSync(
|
|
56
|
+
path.join(DEPO, 'packages/moonui-pro/src/components/ui/badge.tsx'),
|
|
57
|
+
'utf8'
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
type RGB = [number, number, number]
|
|
61
|
+
type Blok = 'light' | 'dark'
|
|
62
|
+
|
|
63
|
+
// ─── Renk aritmetiği (#544/#715 testleriyle birebir aynı yöntem) ────────────────
|
|
64
|
+
|
|
65
|
+
function tokenOku(name: string, blok: Blok): string | null {
|
|
66
|
+
const source =
|
|
67
|
+
blok === 'dark'
|
|
68
|
+
? tokens.slice(tokens.indexOf('.dark'))
|
|
69
|
+
: tokens.slice(0, tokens.indexOf('.dark'))
|
|
70
|
+
return source.match(new RegExp(`--${name}:\\s*([^;]+);`))?.[1].trim() ?? null
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function hslToRgb(h: number, s: number, l: number): RGB {
|
|
74
|
+
h /= 360
|
|
75
|
+
s /= 100
|
|
76
|
+
l /= 100
|
|
77
|
+
const f = (n: number) => {
|
|
78
|
+
const k = (n + h * 12) % 12
|
|
79
|
+
const a = s * Math.min(l, 1 - l)
|
|
80
|
+
return l - a * Math.max(-1, Math.min(k - 3, Math.min(9 - k, 1)))
|
|
81
|
+
}
|
|
82
|
+
return [Math.round(255 * f(0)), Math.round(255 * f(8)), Math.round(255 * f(4))]
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const hslTokenRgb = (v: string): RGB => {
|
|
86
|
+
const [h, s, l] = v.split(/\s+/).map((x) => parseFloat(x))
|
|
87
|
+
return hslToRgb(h, s, l)
|
|
88
|
+
}
|
|
89
|
+
const semanticRgb = (name: string, blok: Blok): RGB =>
|
|
90
|
+
hslTokenRgb(tokenOku(name, blok) ?? tokenOku(name, 'light')!)
|
|
91
|
+
|
|
92
|
+
function relativeLuminance([r, g, b]: RGB): number {
|
|
93
|
+
const c = (v: number) => {
|
|
94
|
+
const x = v / 255
|
|
95
|
+
return x <= 0.03928 ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4)
|
|
96
|
+
}
|
|
97
|
+
return 0.2126 * c(r) + 0.7152 * c(g) + 0.0722 * c(b)
|
|
98
|
+
}
|
|
99
|
+
function kontrast(a: RGB, b: RGB): number {
|
|
100
|
+
const la = relativeLuminance(a)
|
|
101
|
+
const lb = relativeLuminance(b)
|
|
102
|
+
const [hi, lo] = la > lb ? [la, lb] : [lb, la]
|
|
103
|
+
return (hi + 0.05) / (lo + 0.05)
|
|
104
|
+
}
|
|
105
|
+
const hex = ([r, g, b]: RGB): string =>
|
|
106
|
+
'#' + [r, g, b].map((x) => Math.round(x).toString(16).padStart(2, '0')).join('')
|
|
107
|
+
|
|
108
|
+
// ─── Sabitler — HER BİRİ ADIYLA ANILIR ──────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
const AA_TEXT = 4.5
|
|
111
|
+
/** Tailwind varsayılan paleti — `gray-*` ne tokens.css'te ne preset'te tanımlı. */
|
|
112
|
+
const GRAY_100: RGB = [243, 244, 246]
|
|
113
|
+
const GRAY_200: RGB = [229, 231, 235]
|
|
114
|
+
const WHITE: RGB = [255, 255, 255]
|
|
115
|
+
/** Docs kabuğunda ölçülen GERÇEK koyu kart (#284/#448/#454/#715 konvansiyonu) */
|
|
116
|
+
const CARD_ACTUAL_DARK: RGB = [17, 24, 39]
|
|
117
|
+
|
|
118
|
+
/** `DocsHeader.getBadgeClassName()` — beş docs kabuğu dosyasında birebir tekrar eder. */
|
|
119
|
+
const DOCS_PRO_BADGE_CLASS =
|
|
120
|
+
'bg-gradient-to-r from-brand-600 to-brand-accent-600 text-white border-0'
|
|
121
|
+
|
|
122
|
+
// ─── Varyant sınıflarını KAYNAKTAN çöz (#715 ile aynı ayrıştırıcı) ──────────────
|
|
123
|
+
|
|
124
|
+
function variantClass(source: string, variantVal: string): string {
|
|
125
|
+
const m = source.match(new RegExp(`\\n\\s*${variantVal}:\\s*\\[([\\s\\S]*?)\\n\\s*\\]`))
|
|
126
|
+
if (!m) throw new Error(`${variantVal} varyantı bulunamadı`)
|
|
127
|
+
return (m[1].match(/"([^"]*)"/g) ?? []).map((s) => s.slice(1, -1)).join(' ')
|
|
128
|
+
}
|
|
129
|
+
const variantClasses = (source: string, v: string): string[] =>
|
|
130
|
+
variantClass(source, v).split(/\s+/).filter(Boolean)
|
|
131
|
+
|
|
132
|
+
const PAKETLER = [
|
|
133
|
+
['free', freeSrc],
|
|
134
|
+
['pro', proSrc],
|
|
135
|
+
] as const
|
|
136
|
+
|
|
137
|
+
const badgeCls = (jsx: React.ReactElement): string[] => {
|
|
138
|
+
const { container } = render(jsx)
|
|
139
|
+
return (container.firstElementChild as HTMLElement).className.split(/\s+/)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ════════════════════════════════════════════════════════════════════════════════
|
|
143
|
+
// Kusurun ÖLÇÜSÜ — düzeltmeden bağımsız, iddiayı kilitler
|
|
144
|
+
// ════════════════════════════════════════════════════════════════════════════════
|
|
145
|
+
|
|
146
|
+
describe('#708 kalem 3 — kusurun ölçüsü (regresyon çıpası)', () => {
|
|
147
|
+
it('gradyan zemin iki temada BİREBİR aynı — `--brand-*` `.dark` ezmesi yok', () => {
|
|
148
|
+
for (const t of ['brand-600', 'brand-accent-600']) {
|
|
149
|
+
// `.dark` bloğunda hiç tanım YOK → `:root` değeri iki temada da geçerli.
|
|
150
|
+
expect(`${t} dark ezmesi: ${tokenOku(t, 'dark')}`).toBe(`${t} dark ezmesi: null`)
|
|
151
|
+
expect(tokenOku(t, 'light')).not.toBeNull()
|
|
152
|
+
}
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
it('tüketicinin istediği BEYAZ gradyanın iki ucunda da AA geçiyor', () => {
|
|
156
|
+
for (const t of ['brand-600', 'brand-accent-600']) {
|
|
157
|
+
const zemin = semanticRgb(t, 'light')
|
|
158
|
+
const oran = kontrast(WHITE, zemin)
|
|
159
|
+
expect(`${t} ${hex(zemin)} beyaz: ${oran >= AA_TEXT ? 'geçer' : oran.toFixed(2)}`).toBe(
|
|
160
|
+
`${t} ${hex(zemin)} beyaz: geçer`
|
|
161
|
+
)
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
it('varyantın DAYATTIĞI koyu-tema metni AA ALTINDA (kusurun kendisi)', () => {
|
|
166
|
+
// A18-V'nin ölçtüğü en kötü uç: `to-brand-accent-600`.
|
|
167
|
+
const enKotu = semanticRgb('brand-accent-600', 'light')
|
|
168
|
+
const gray100 = kontrast(GRAY_100, enKotu)
|
|
169
|
+
const gray200 = kontrast(GRAY_200, enKotu)
|
|
170
|
+
expect(gray100).toBeLessThan(AA_TEXT)
|
|
171
|
+
expect(gray100).toBeCloseTo(4.18, 1) // raporlanan 4.177
|
|
172
|
+
// pro ikizi `gray-200` kullanıyordu — o daha da kötü
|
|
173
|
+
expect(gray200).toBeLessThan(gray100)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('öneksiz `text-foreground` de bu gradyanda AA taşımaz → çözüm "token" değil', () => {
|
|
177
|
+
// Yani `dark:text-gray-100` yerine `text-foreground` bırakmak DA yetmezdi;
|
|
178
|
+
// kurtaran şey tüketicinin `text-white`ının artık EZİLMEMESİ.
|
|
179
|
+
const enKotu = semanticRgb('brand-accent-600', 'light')
|
|
180
|
+
expect(kontrast(semanticRgb('foreground', 'dark'), enKotu)).toBeLessThan(AA_TEXT)
|
|
181
|
+
})
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
// ════════════════════════════════════════════════════════════════════════════════
|
|
185
|
+
// Düzeltme — `outline` artık tüketiciyi EZMİYOR
|
|
186
|
+
// ════════════════════════════════════════════════════════════════════════════════
|
|
187
|
+
|
|
188
|
+
describe('#708 kalem 3 — `outline` varyantı tüketici override\'ını ezmiyor', () => {
|
|
189
|
+
it.each(PAKETLER)('%s: `outline` dizisinde hiç `dark:text-*` YOK', (_ad, src) => {
|
|
190
|
+
expect(variantClasses(src, 'outline').filter((c) => /^dark:text-/.test(c))).toEqual([])
|
|
191
|
+
})
|
|
192
|
+
|
|
193
|
+
it.each(PAKETLER)('%s: `dark:bg-transparent` SAF TEKRARDI, kaldırıldı', (_ad, src) => {
|
|
194
|
+
const liste = variantClasses(src, 'outline')
|
|
195
|
+
expect(liste).toContain('bg-transparent') // öneksiz ikiz KORUNDU
|
|
196
|
+
expect(liste).not.toContain('dark:bg-transparent')
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
it.each(PAKETLER)('%s: sıralama guard\'ı gerekmiyor — rakip `dark:` taban kuralı yok', (_ad, src) => {
|
|
200
|
+
// #715'in ikinci dersi: `dark:X` bir guard olabilir, ama yalnız varyant AYNI
|
|
201
|
+
// özellik için FARKLI değerli bir `dark:` taban kuralı bildiriyorsa.
|
|
202
|
+
const liste = variantClasses(src, 'outline')
|
|
203
|
+
const ozellik = (c: string) => c.replace(/^(?:[a-z-]+:)+/, '').match(/^(bg|text)-/)?.[1]
|
|
204
|
+
const rakipler = liste
|
|
205
|
+
.filter((c) => /^dark:(?!hover:|active:|focus-visible:)/.test(c))
|
|
206
|
+
.filter((c) => !liste.includes(c.slice('dark:'.length)))
|
|
207
|
+
.map(ozellik)
|
|
208
|
+
.filter(Boolean)
|
|
209
|
+
expect(rakipler).toEqual([])
|
|
210
|
+
})
|
|
211
|
+
|
|
212
|
+
it.each(PAKETLER)('%s: `dark:backdrop-blur-sm` KORUNDU (öneksiz ikizi yok)', (_ad, src) => {
|
|
213
|
+
expect(variantClasses(src, 'outline')).toContain('dark:backdrop-blur-sm')
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it.each(PAKETLER)('%s: kaynakta `dark:text-gray-*` hiç kalmadı (`outline` dalı)', (_ad, src) => {
|
|
217
|
+
// Yorum satırları issue'yu anlatmak için sınıf adını LİTERAL içeriyor →
|
|
218
|
+
// yorumsuz gövde üzerinden bak (yoksa test kendi belgesine takılır).
|
|
219
|
+
const yorumsuz = src.replace(/\/\*[\s\S]*?\*\//g, '').replace(/^\s*\/\/.*$/gm, '')
|
|
220
|
+
expect(yorumsuz).not.toContain('dark:text-gray-100 dark:bg-transparent')
|
|
221
|
+
expect(yorumsuz).not.toContain('dark:text-gray-200 dark:bg-transparent')
|
|
222
|
+
})
|
|
223
|
+
})
|
|
224
|
+
|
|
225
|
+
describe('#708 kalem 3 — GERÇEK docs vakası (DocsHeader "PRO" rozeti)', () => {
|
|
226
|
+
it('`text-white` sağ kalıyor ve onu yenecek `dark:text-*` YOK', () => {
|
|
227
|
+
const parcalar = badgeCls(
|
|
228
|
+
<Badge variant="outline" className={cn('text-xs', DOCS_PRO_BADGE_CLASS)}>
|
|
229
|
+
PRO
|
|
230
|
+
</Badge>
|
|
231
|
+
)
|
|
232
|
+
expect(parcalar).toContain('text-white')
|
|
233
|
+
// Kusurun imzası: twMerge öneksizi eler, `dark:` ikizi kalır ve (0,2,0) ile
|
|
234
|
+
// tüketicinin (0,1,0) sınıfını yener. Hiç `dark:text-*` kalmadıysa mümkün değil.
|
|
235
|
+
expect(parcalar.filter((x) => x.startsWith('dark:text-'))).toEqual([])
|
|
236
|
+
expect(parcalar).not.toContain('text-foreground')
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('gradyan zemin sağ kalıyor — `dark:bg-transparent` onu boyamıyor', () => {
|
|
240
|
+
const parcalar = badgeCls(
|
|
241
|
+
<Badge variant="outline" className={cn('text-xs', DOCS_PRO_BADGE_CLASS)}>
|
|
242
|
+
PRO
|
|
243
|
+
</Badge>
|
|
244
|
+
)
|
|
245
|
+
expect(parcalar).toContain('bg-gradient-to-r')
|
|
246
|
+
expect(parcalar).toContain('from-brand-600')
|
|
247
|
+
expect(parcalar).toContain('to-brand-accent-600')
|
|
248
|
+
// ⚠️ twMerge `bg-gradient-to-r`(background-image) ile `bg-transparent`
|
|
249
|
+
// (background-color) sınıflarını AYNI grupta saymaz → öneksiz olan sağ kalır
|
|
250
|
+
// (gradyan onu boyar, sorun değil). Tehlikeli olan `dark:` ikiziydi.
|
|
251
|
+
expect(parcalar).not.toContain('dark:bg-transparent')
|
|
252
|
+
})
|
|
253
|
+
|
|
254
|
+
it('`DocsNavigation`in `scale-75`li ikizi de aynı sonucu veriyor', () => {
|
|
255
|
+
const parcalar = badgeCls(
|
|
256
|
+
<Badge variant="outline" className={`${DOCS_PRO_BADGE_CLASS} text-xs scale-75`}>
|
|
257
|
+
PRO
|
|
258
|
+
</Badge>
|
|
259
|
+
)
|
|
260
|
+
expect(parcalar).toContain('text-white')
|
|
261
|
+
expect(parcalar.filter((x) => x.startsWith('dark:text-'))).toEqual([])
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
describe('#708 kalem 3 — KONTROL GRUBU: rozetin KENDİ görünümü bozulmadı', () => {
|
|
266
|
+
it.each(PAKETLER)('%s: `outline` hâlâ `bg-transparent text-foreground` çifti', (_ad, src) => {
|
|
267
|
+
expect(variantClass(src, 'outline')).toContain('bg-transparent text-foreground')
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
it('tüketici sınıfı YOKKEN metin `--foreground` — iki temada da AA üstü', () => {
|
|
271
|
+
// `dark:text-gray-100` kalkınca koyu temada metin gray-100 yerine
|
|
272
|
+
// `--foreground` (dark) oluyor. Fark ölçüldü: ikisi de rahatça geçiyor,
|
|
273
|
+
// yani kaldırmanın rozetin kendi okunurluğuna maliyeti YOK.
|
|
274
|
+
const eski = kontrast(GRAY_100, CARD_ACTUAL_DARK)
|
|
275
|
+
const yeni = kontrast(semanticRgb('foreground', 'dark'), CARD_ACTUAL_DARK)
|
|
276
|
+
expect(eski).toBeGreaterThanOrEqual(AA_TEXT)
|
|
277
|
+
expect(yeni).toBeGreaterThanOrEqual(AA_TEXT)
|
|
278
|
+
// "az bir fark" iddiasının kilidi — 2 kattan fazla ayrılırlarsa test düşsün
|
|
279
|
+
expect(Math.abs(eski - yeni)).toBeLessThan(2)
|
|
280
|
+
})
|
|
281
|
+
|
|
282
|
+
it('açık temada davranış hiç değişmedi (`text-foreground` zaten öneksizdi)', () => {
|
|
283
|
+
const parcalar = badgeCls(<Badge variant="outline">Free</Badge>)
|
|
284
|
+
expect(parcalar).toContain('text-foreground')
|
|
285
|
+
expect(parcalar).toContain('bg-transparent')
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
it.each(PAKETLER)('%s: diğer varyantlar #708 kapsamı DIŞINDA — dokunulmadı', (_ad, src) => {
|
|
289
|
+
// `primary` #646'da, `destructive` #544/#715'te ele alındı; #708 yalnız
|
|
290
|
+
// `outline` dalını değiştirir.
|
|
291
|
+
expect(variantClass(src, 'primary')).toContain('bg-primary text-primary-foreground')
|
|
292
|
+
expect(variantClass(src, 'destructive')).toContain('bg-error-600 text-white')
|
|
293
|
+
})
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
describe('#708 kalem 3 — free ↔ pro İKİZ EŞİTLİĞİ', () => {
|
|
297
|
+
const outlineOf = (src: string) => variantClasses(src, 'outline')
|
|
298
|
+
|
|
299
|
+
it('`outline`ın METİN sınıfları iki pakette birebir aynı', () => {
|
|
300
|
+
const metin = (src: string) => outlineOf(src).filter((c) => /(^|:)text-/.test(c))
|
|
301
|
+
expect(metin(proSrc)).toEqual(metin(freeSrc))
|
|
302
|
+
expect(metin(freeSrc)).toEqual(['text-foreground'])
|
|
303
|
+
})
|
|
304
|
+
|
|
305
|
+
it('`outline`ın ARKA PLAN sınıfları iki pakette birebir aynı', () => {
|
|
306
|
+
const zemin = (src: string) => outlineOf(src).filter((c) => /(^|:)bg-/.test(c))
|
|
307
|
+
expect(zemin(proSrc)).toEqual(zemin(freeSrc))
|
|
308
|
+
expect(zemin(freeSrc)).toEqual(['bg-transparent'])
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
it('ikisinde de `dark:` yalnız KENARLIK, RING ve BLUR için kaldı', () => {
|
|
312
|
+
for (const src of [freeSrc, proSrc]) {
|
|
313
|
+
const kalanlar = outlineOf(src)
|
|
314
|
+
.filter((c) => c.startsWith('dark:'))
|
|
315
|
+
.map((c) => c.replace(/^dark:(?:hover:)?/, '').replace(/-.*/, ''))
|
|
316
|
+
expect([...new Set(kalanlar)].sort()).toEqual(['backdrop', 'border', 'focus'])
|
|
317
|
+
}
|
|
318
|
+
})
|
|
319
|
+
})
|