@dialpad/dialtone-css 8.80.0-next.4 → 8.80.0-next.5

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.
Files changed (127) hide show
  1. package/lib/build/js/dialtone_migrate_chip_interactive/index.mjs +367 -0
  2. package/lib/build/js/dialtone_migrate_chip_interactive/test.mjs +244 -0
  3. package/lib/build/js/dialtone_migrate_scrollbar_always/index.mjs +225 -0
  4. package/lib/build/less/components/box.less +2 -0
  5. package/lib/build/less/components/link.less +14 -19
  6. package/lib/build/less/components/notice.less +1 -1
  7. package/lib/build/less/components/scrollbar.less +22 -0
  8. package/lib/build/less/dialtone.less +7 -0
  9. package/lib/dist/dialtone-default-theme.css +316 -293
  10. package/lib/dist/dialtone-default-theme.min.css +1 -1
  11. package/lib/dist/dialtone-docs.json +1 -1
  12. package/lib/dist/dialtone.css +32 -15
  13. package/lib/dist/dialtone.min.css +1 -1
  14. package/lib/dist/js/dialtone_migrate_chip_interactive/index.mjs +367 -0
  15. package/lib/dist/js/dialtone_migrate_chip_interactive/test.mjs +244 -0
  16. package/lib/dist/js/dialtone_migrate_scrollbar_always/index.mjs +225 -0
  17. package/lib/dist/tokens/tokens-101-dark.css +141 -135
  18. package/lib/dist/tokens/tokens-101-light.css +164 -158
  19. package/lib/dist/tokens/tokens-102-dark.css +141 -135
  20. package/lib/dist/tokens/tokens-102-light.css +164 -158
  21. package/lib/dist/tokens/tokens-103-dark.css +141 -135
  22. package/lib/dist/tokens/tokens-103-light.css +164 -158
  23. package/lib/dist/tokens/tokens-104-dark.css +141 -135
  24. package/lib/dist/tokens/tokens-104-light.css +164 -158
  25. package/lib/dist/tokens/tokens-105-dark.css +141 -135
  26. package/lib/dist/tokens/tokens-105-light.css +164 -158
  27. package/lib/dist/tokens/tokens-106-dark.css +141 -135
  28. package/lib/dist/tokens/tokens-106-light.css +164 -158
  29. package/lib/dist/tokens/tokens-107-dark.css +141 -135
  30. package/lib/dist/tokens/tokens-107-light.css +164 -158
  31. package/lib/dist/tokens/tokens-108-dark.css +141 -135
  32. package/lib/dist/tokens/tokens-108-light.css +164 -158
  33. package/lib/dist/tokens/tokens-109-dark.css +141 -135
  34. package/lib/dist/tokens/tokens-109-light.css +164 -158
  35. package/lib/dist/tokens/tokens-110-dark.css +141 -135
  36. package/lib/dist/tokens/tokens-110-light.css +164 -158
  37. package/lib/dist/tokens/tokens-111-dark.css +141 -135
  38. package/lib/dist/tokens/tokens-111-light.css +164 -158
  39. package/lib/dist/tokens/tokens-112-dark.css +141 -135
  40. package/lib/dist/tokens/tokens-112-light.css +164 -158
  41. package/lib/dist/tokens/tokens-113-dark.css +141 -135
  42. package/lib/dist/tokens/tokens-113-light.css +164 -158
  43. package/lib/dist/tokens/tokens-114-dark.css +141 -135
  44. package/lib/dist/tokens/tokens-114-light.css +164 -158
  45. package/lib/dist/tokens/tokens-115-dark.css +141 -135
  46. package/lib/dist/tokens/tokens-115-light.css +164 -158
  47. package/lib/dist/tokens/tokens-116-dark.css +141 -135
  48. package/lib/dist/tokens/tokens-116-light.css +164 -158
  49. package/lib/dist/tokens/tokens-117-dark.css +141 -135
  50. package/lib/dist/tokens/tokens-117-light.css +164 -158
  51. package/lib/dist/tokens/tokens-118-dark.css +141 -135
  52. package/lib/dist/tokens/tokens-118-light.css +164 -158
  53. package/lib/dist/tokens/tokens-119-dark.css +141 -135
  54. package/lib/dist/tokens/tokens-119-light.css +164 -158
  55. package/lib/dist/tokens/tokens-120-dark.css +141 -135
  56. package/lib/dist/tokens/tokens-120-light.css +164 -158
  57. package/lib/dist/tokens/tokens-121-dark.css +141 -135
  58. package/lib/dist/tokens/tokens-121-light.css +164 -158
  59. package/lib/dist/tokens/tokens-122-dark.css +141 -135
  60. package/lib/dist/tokens/tokens-122-light.css +164 -158
  61. package/lib/dist/tokens/tokens-123-dark.css +141 -135
  62. package/lib/dist/tokens/tokens-123-light.css +164 -158
  63. package/lib/dist/tokens/tokens-124-dark.css +141 -135
  64. package/lib/dist/tokens/tokens-124-light.css +164 -158
  65. package/lib/dist/tokens/tokens-125-dark.css +141 -135
  66. package/lib/dist/tokens/tokens-125-light.css +164 -158
  67. package/lib/dist/tokens/tokens-126-dark.css +141 -135
  68. package/lib/dist/tokens/tokens-126-light.css +164 -158
  69. package/lib/dist/tokens/tokens-127-dark.css +141 -135
  70. package/lib/dist/tokens/tokens-127-light.css +164 -158
  71. package/lib/dist/tokens/tokens-128-dark.css +141 -135
  72. package/lib/dist/tokens/tokens-128-light.css +164 -158
  73. package/lib/dist/tokens/tokens-129-dark.css +141 -135
  74. package/lib/dist/tokens/tokens-129-light.css +164 -158
  75. package/lib/dist/tokens/tokens-130-dark.css +141 -135
  76. package/lib/dist/tokens/tokens-130-light.css +164 -158
  77. package/lib/dist/tokens/tokens-131-dark.css +141 -135
  78. package/lib/dist/tokens/tokens-131-light.css +164 -158
  79. package/lib/dist/tokens/tokens-132-dark.css +141 -135
  80. package/lib/dist/tokens/tokens-132-light.css +164 -158
  81. package/lib/dist/tokens/tokens-133-dark.css +141 -135
  82. package/lib/dist/tokens/tokens-133-light.css +164 -158
  83. package/lib/dist/tokens/tokens-134-dark.css +141 -135
  84. package/lib/dist/tokens/tokens-134-light.css +164 -158
  85. package/lib/dist/tokens/tokens-135-dark.css +141 -135
  86. package/lib/dist/tokens/tokens-135-light.css +164 -158
  87. package/lib/dist/tokens/tokens-136-dark.css +141 -135
  88. package/lib/dist/tokens/tokens-136-light.css +164 -158
  89. package/lib/dist/tokens/tokens-137-dark.css +141 -135
  90. package/lib/dist/tokens/tokens-137-light.css +164 -158
  91. package/lib/dist/tokens/tokens-aegean-dark.css +164 -158
  92. package/lib/dist/tokens/tokens-aegean-light.css +184 -178
  93. package/lib/dist/tokens/tokens-base-dark.css +98 -98
  94. package/lib/dist/tokens/tokens-base-light.css +98 -98
  95. package/lib/dist/tokens/tokens-botany-dark.css +149 -143
  96. package/lib/dist/tokens/tokens-botany-light.css +171 -165
  97. package/lib/dist/tokens/tokens-buttercream-dark.css +148 -142
  98. package/lib/dist/tokens/tokens-buttercream-light.css +171 -165
  99. package/lib/dist/tokens/tokens-ceruleo-dark.css +164 -158
  100. package/lib/dist/tokens/tokens-ceruleo-light.css +184 -178
  101. package/lib/dist/tokens/tokens-contrast-high-dark.css +3 -3
  102. package/lib/dist/tokens/tokens-contrast-high-light.css +10 -10
  103. package/lib/dist/tokens/tokens-debug-dp.css +28 -22
  104. package/lib/dist/tokens/tokens-dp-dark.css +166 -160
  105. package/lib/dist/tokens/tokens-dp-light.css +186 -180
  106. package/lib/dist/tokens/tokens-expressive-dark.css +163 -157
  107. package/lib/dist/tokens/tokens-expressive-light.css +186 -180
  108. package/lib/dist/tokens/tokens-expressive-sm-dark.css +163 -157
  109. package/lib/dist/tokens/tokens-expressive-sm-light.css +186 -180
  110. package/lib/dist/tokens/tokens-high-desert-dark.css +144 -138
  111. package/lib/dist/tokens/tokens-high-desert-light.css +164 -158
  112. package/lib/dist/tokens/tokens-melon-dark.css +145 -139
  113. package/lib/dist/tokens/tokens-melon-light.css +165 -159
  114. package/lib/dist/tokens/tokens-plum-dark.css +153 -147
  115. package/lib/dist/tokens/tokens-plum-light.css +164 -158
  116. package/lib/dist/tokens/tokens-prota-deuter-dark.css +292 -262
  117. package/lib/dist/tokens/tokens-prota-deuter-light.css +315 -285
  118. package/lib/dist/tokens/tokens-sunflower-dark.css +154 -148
  119. package/lib/dist/tokens/tokens-sunflower-light.css +174 -168
  120. package/lib/dist/tokens/tokens-tmo-dark.css +165 -159
  121. package/lib/dist/tokens/tokens-tmo-light.css +185 -179
  122. package/lib/dist/tokens/tokens-trita-dark.css +239 -197
  123. package/lib/dist/tokens/tokens-trita-light.css +263 -221
  124. package/lib/dist/tokens/tokens-verdant-haze-dark.css +144 -138
  125. package/lib/dist/tokens/tokens-verdant-haze-light.css +164 -158
  126. package/lib/dist/tokens-docs.json +1 -1
  127. package/package.json +4 -2
@@ -0,0 +1,367 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @fileoverview Migration script for DtChip `interactive` prop default change.
5
+ *
6
+ * DLT-3195 DtChip `interactive` prop default changed from `true` → `false`.
7
+ * Chips that need click/keyboard behavior must now explicitly set
8
+ * `:interactive="true"`.
9
+ *
10
+ * This script:
11
+ * - Adds `:interactive="true"` to <dt-chip> tags that have a click event
12
+ * listener (@click, v-on:click) or an object v-on binding (v-on="…"),
13
+ * since those clearly need interactive behavior.
14
+ * - Skips chips that already set the `interactive` prop (any form).
15
+ * - Warns about remaining chips with no `interactive` prop and no detected
16
+ * click handler — these may be display-only (no change needed) or may
17
+ * need `:interactive="true"` added manually.
18
+ *
19
+ * Usage:
20
+ * npx dialtone-migrate-chip-interactive [options]
21
+ *
22
+ * Options:
23
+ * --cwd <path> Working directory (default: cwd)
24
+ * --dry-run Show changes without applying them
25
+ * --yes Apply all changes without prompting
26
+ * --help Show help
27
+ */
28
+
29
+ import fs from 'fs/promises';
30
+ import { realpathSync } from 'node:fs';
31
+ import path from 'path';
32
+ import readline from 'readline';
33
+ import { fileURLToPath } from 'node:url';
34
+
35
+ // ---------------------------------------------------------------------------
36
+ // Constants
37
+ // ---------------------------------------------------------------------------
38
+
39
+ // Quote-aware attribute body. Matches sequences of non-quote/non-gt chars
40
+ // optionally followed by a fully-quoted attribute value, so `>` inside a
41
+ // quoted value like `:class="a > b"` does not prematurely terminate the tag.
42
+ const QUOTE_AWARE_ATTRS = '(?:[^>"\']|"[^"]*"|\'[^\']*\')*';
43
+
44
+ // Matches `<dt-chip` or `<DtChip` opening tags (including self-closing).
45
+ // Group 1: tag name; group 2: attributes (quote-aware); group 3: closer (`>` or `/>`).
46
+ const CHIP_TAG_RE = new RegExp(
47
+ `(<(?:dt-chip|DtChip)\\b)(${QUOTE_AWARE_ATTRS})(\\s*\\/?>)`,
48
+ 'g',
49
+ );
50
+
51
+ // Detects that the `interactive` prop is already present in any form:
52
+ // interactive, :interactive, v-bind:interactive
53
+ const HAS_INTERACTIVE_RE = /(?:^|\s)(?::|v-bind:)?interactive(?:\s*=|\s|\/|>)/;
54
+
55
+ // Detects a click event listener:
56
+ // @click, v-on:click, @click.stop, @click.prevent, etc.
57
+ const HAS_CLICK_RE = /(?:^|\s)(?:@click|v-on:click)(?:\s*=|\s*\.|\/|>|\s)/;
58
+
59
+ // Detects an object-form v-on binding (v-on="…") which may contain click.
60
+ // We treat this conservatively as "may be interactive" and add the prop.
61
+ const HAS_VON_OBJECT_RE = /(?:^|\s)v-on\s*=\s*(?:"[^"]*"|'[^']*')/;
62
+
63
+ // ---------------------------------------------------------------------------
64
+ // Transform
65
+ // ---------------------------------------------------------------------------
66
+
67
+ /**
68
+ * Strip quoted attribute values from an attrs string before regex testing.
69
+ * Replaces "…" and '…' with empty equivalents so keywords that happen to
70
+ * appear inside a quoted value (e.g. :title=" @click is cool") don't
71
+ * false-positive against HAS_CLICK_RE / HAS_INTERACTIVE_RE / HAS_VON_OBJECT_RE.
72
+ * Real attribute tokens like @click="handler" survive as @click="" and still match.
73
+ */
74
+ function stripQuotedValues (attrs) {
75
+ return attrs.replace(/"[^"]*"|'[^']*'/g, match => (match[0] === '"' ? '""' : '\'\''));
76
+ }
77
+
78
+ /**
79
+ * Find the position to insert `:interactive="true"` — immediately after the
80
+ * tag name so it appears first in the attribute list (consistent with existing
81
+ * Dialtone convention where `:interactive` is an early structural prop).
82
+ *
83
+ * Returns the index within `attrs` where the insertion should happen.
84
+ * We insert after any leading whitespace on the attribute string.
85
+ */
86
+ function insertInteractiveProp (attrs) {
87
+ const leadingSpace = attrs.match(/^\s*/)[0];
88
+ const rest = attrs.slice(leadingSpace.length);
89
+ // Preserve the original leading whitespace, then prepend the prop
90
+ return `${leadingSpace}:interactive="true" ${rest}`;
91
+ }
92
+
93
+ /**
94
+ * Transform a single file's content.
95
+ * Returns { transformed, warnings } where warnings are strings.
96
+ */
97
+ export function transformContent (content, opts = {}) {
98
+ const filePath = opts.filePath || '<input>';
99
+ const warnings = [];
100
+
101
+ // Fast path: skip files with no dt-chip / DtChip reference at all.
102
+ if (!/(?:dt-chip|DtChip)/i.test(content)) {
103
+ return { transformed: content, warnings };
104
+ }
105
+
106
+ // Mask inert content (HTML comments, <script>, <style>) so we don't
107
+ // accidentally match tag-like text inside them.
108
+ const { masked, segments, token } = maskInertContent(content);
109
+
110
+ let out = masked;
111
+ const replacements = [];
112
+
113
+ // Reset lastIndex before iterating
114
+ CHIP_TAG_RE.lastIndex = 0;
115
+
116
+ let m;
117
+ while ((m = CHIP_TAG_RE.exec(out)) !== null) {
118
+ const [fullMatch, openTag, attrs, closer] = m;
119
+ const matchStart = m.index;
120
+ const matchEnd = matchStart + fullMatch.length;
121
+
122
+ // Strip quoted values before regex testing so keywords inside quoted
123
+ // attribute values don't produce false positives.
124
+ const attrsForTest = stripQuotedValues(attrs);
125
+
126
+ // Already has the interactive prop — nothing to do.
127
+ if (HAS_INTERACTIVE_RE.test(attrsForTest)) continue;
128
+
129
+ const hasClick = HAS_CLICK_RE.test(attrsForTest);
130
+ const hasVOnObject = HAS_VON_OBJECT_RE.test(attrsForTest);
131
+
132
+ if (hasClick || hasVOnObject) {
133
+ // Auto-add :interactive="true"
134
+ const newAttrs = insertInteractiveProp(attrs);
135
+ replacements.push({
136
+ start: matchStart,
137
+ end: matchEnd,
138
+ text: `${openTag}${newAttrs}${closer}`,
139
+ });
140
+ } else {
141
+ // No click handler and no interactive prop.
142
+ // Warn: this chip will now render as a <span>. May be intentional
143
+ // (display-only) or may need :interactive="true" manually.
144
+ warnings.push(
145
+ `${filePath}: <dt-chip> has no interactive prop and no @click handler — ` +
146
+ `will now render as a non-interactive <span>. ` +
147
+ `Add :interactive="true" if this chip should be clickable.`,
148
+ );
149
+ }
150
+ }
151
+
152
+ // Apply replacements in reverse order to preserve indices
153
+ replacements.sort((a, b) => b.start - a.start);
154
+ for (const r of replacements) {
155
+ out = out.slice(0, r.start) + r.text + out.slice(r.end);
156
+ }
157
+
158
+ return { transformed: unmaskInertContent(out, segments, token), warnings };
159
+ }
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // Inert-content masking (same pattern as dialtone_migrate_link_rendering)
163
+ // ---------------------------------------------------------------------------
164
+
165
+ function maskInertContent (content) {
166
+ const token = Math.random().toString(36).slice(2, 10);
167
+ const innerRe = /<!--[\s\S]*?-->|<script\b[^>]*>[\s\S]*?<\/script>|<style\b[^>]*>[\s\S]*?<\/style>/g;
168
+ const segments = [];
169
+ const masked = content.replace(innerRe, (match) => {
170
+ const placeholder = ` DT_MIGRATE_INERT_${token}_${segments.length} `;
171
+ segments.push(match);
172
+ return placeholder;
173
+ });
174
+ return { masked, segments, token };
175
+ }
176
+
177
+ function unmaskInertContent (masked, segments, token) {
178
+ return masked.replace(new RegExp(` DT_MIGRATE_INERT_${token}_(\\d+) `, 'g'), (_, idx) => segments[Number(idx)]);
179
+ }
180
+
181
+ // ---------------------------------------------------------------------------
182
+ // File walker
183
+ // ---------------------------------------------------------------------------
184
+
185
+ function isIgnoredPath (fullPath, ignore) {
186
+ const segments = fullPath.split(path.sep);
187
+ return ignore.some(ig => {
188
+ if (ig.includes('/')) {
189
+ const parts = ig.split('/');
190
+ for (let i = 0; i + parts.length <= segments.length; i++) {
191
+ if (parts.every((p, j) => segments[i + j] === p)) return true;
192
+ }
193
+ return false;
194
+ }
195
+ return segments.includes(ig);
196
+ });
197
+ }
198
+
199
+ async function findFiles (dir, extensions, ignore = []) {
200
+ const results = [];
201
+ async function walk (currentDir) {
202
+ let entries;
203
+ try {
204
+ entries = await fs.readdir(currentDir, { withFileTypes: true });
205
+ } catch {
206
+ return;
207
+ }
208
+ for (const entry of entries) {
209
+ const fullPath = path.join(currentDir, entry.name);
210
+ if (isIgnoredPath(fullPath, ignore)) continue;
211
+ if (entry.isDirectory()) {
212
+ await walk(fullPath);
213
+ } else if (entry.isFile() && extensions.some(ext => entry.name.endsWith(ext))) {
214
+ results.push(fullPath);
215
+ }
216
+ }
217
+ }
218
+ await walk(dir);
219
+ return results;
220
+ }
221
+
222
+ // ---------------------------------------------------------------------------
223
+ // CLI plumbing
224
+ // ---------------------------------------------------------------------------
225
+
226
+ function printHelp () {
227
+ console.log(`
228
+ Usage: npx dialtone-migrate-chip-interactive [options]
229
+
230
+ Migrates DtChip usage after the \`interactive\` prop default changed from
231
+ \`true\` to \`false\` (DLT-3195).
232
+
233
+ Chips with a @click handler or v-on object binding automatically receive
234
+ :interactive="true". All other chips without an existing interactive prop
235
+ are listed as warnings for manual review.
236
+
237
+ Options:
238
+ --cwd <path> Working directory (default: cwd)
239
+ --dry-run Show changes without applying them
240
+ --yes Apply all changes without prompting
241
+ --help Show help
242
+
243
+ Examples:
244
+ npx dialtone-migrate-chip-interactive
245
+ npx dialtone-migrate-chip-interactive --dry-run
246
+ npx dialtone-migrate-chip-interactive --cwd ./src
247
+ npx dialtone-migrate-chip-interactive --yes
248
+ `);
249
+ }
250
+
251
+ function parseArgs (args) {
252
+ const cwdIndex = args.indexOf('--cwd');
253
+ return {
254
+ help: args.includes('--help'),
255
+ dryRun: args.includes('--dry-run'),
256
+ autoYes: args.includes('--yes'),
257
+ cwd: cwdIndex !== -1 && args[cwdIndex + 1]
258
+ ? path.resolve(args[cwdIndex + 1])
259
+ : process.cwd(),
260
+ };
261
+ }
262
+
263
+ async function prompt (question) {
264
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
265
+ return new Promise(resolve => {
266
+ rl.question(question, answer => {
267
+ rl.close();
268
+ resolve(answer.trim().toLowerCase());
269
+ });
270
+ });
271
+ }
272
+
273
+ async function scanFiles (cwd) {
274
+ const extensions = ['.vue'];
275
+ const ignore = ['node_modules', 'dist', '.git', '.vuepress/public', '.vuepress/.temp', '.vuepress/.cache'];
276
+ const files = await findFiles(cwd, extensions, ignore);
277
+
278
+ const changes = [];
279
+ const allWarnings = [];
280
+
281
+ for (const file of files) {
282
+ const content = await fs.readFile(file, 'utf8');
283
+ const { transformed, warnings } = transformContent(content, {
284
+ filePath: path.relative(cwd, file),
285
+ });
286
+ if (transformed !== content) {
287
+ changes.push({ file, content, transformed });
288
+ }
289
+ if (warnings.length) allWarnings.push(...warnings);
290
+ }
291
+
292
+ return { changes, allWarnings };
293
+ }
294
+
295
+ async function applyChanges (changes, autoYes) {
296
+ if (!autoYes) {
297
+ const answer = await prompt('\nApply changes? (y/N) ');
298
+ if (answer !== 'y' && answer !== 'yes') {
299
+ console.log('Cancelled.');
300
+ return false;
301
+ }
302
+ }
303
+ for (const { file, transformed } of changes) {
304
+ await fs.writeFile(file, transformed, 'utf8');
305
+ }
306
+ return true;
307
+ }
308
+
309
+ function printWarnings (warnings) {
310
+ if (!warnings.length) return;
311
+ console.log('\nWarnings — manual review required:\n');
312
+ for (const w of warnings) console.log(` ${w}`);
313
+ console.log();
314
+ }
315
+
316
+ function printChangeSummary (changes, cwd) {
317
+ console.log(`\nFound changes in ${changes.length} file(s):\n`);
318
+ for (const { file } of changes) {
319
+ console.log(` ${path.relative(cwd, file)}`);
320
+ }
321
+ }
322
+
323
+ async function main () {
324
+ const opts = parseArgs(process.argv.slice(2));
325
+ if (opts.help) {
326
+ printHelp();
327
+ process.exit(0);
328
+ }
329
+
330
+ console.log(`\nScanning ${opts.cwd} for DtChip usages...`);
331
+
332
+ const { changes, allWarnings } = await scanFiles(opts.cwd);
333
+
334
+ printWarnings(allWarnings);
335
+
336
+ if (changes.length === 0) {
337
+ console.log(allWarnings.length
338
+ ? 'No automated code changes needed. See manual review items above.'
339
+ : 'No DtChip usages found. Nothing to migrate.');
340
+ process.exit(0);
341
+ }
342
+
343
+ printChangeSummary(changes, opts.cwd);
344
+
345
+ if (opts.dryRun) {
346
+ console.log('\n--dry-run: No files were modified.');
347
+ process.exit(0);
348
+ }
349
+
350
+ const applied = await applyChanges(changes, opts.autoYes);
351
+ if (applied) console.log(`\nMigrated ${changes.length} file(s).\n`);
352
+ }
353
+
354
+ const isDirectRun = (() => {
355
+ try {
356
+ return realpathSync(process.argv[1]) === fileURLToPath(import.meta.url);
357
+ } catch {
358
+ return false;
359
+ }
360
+ })();
361
+
362
+ if (isDirectRun) {
363
+ main().catch(err => {
364
+ console.error(err);
365
+ process.exit(1);
366
+ });
367
+ }
@@ -0,0 +1,244 @@
1
+ /**
2
+ * DLT-3195 — dialtone-migrate-chip-interactive tests.
3
+ *
4
+ * One assertion per test; data-driven via for..of where multiple cases share a concept.
5
+ */
6
+
7
+ import { describe, it } from 'node:test';
8
+ import assert from 'node:assert/strict';
9
+ import { transformContent } from './index.mjs';
10
+
11
+ function run (input) {
12
+ const { transformed } = transformContent(input, { filePath: 'test.vue' });
13
+ return transformed;
14
+ }
15
+
16
+ function warnings (input) {
17
+ return transformContent(input, { filePath: 'test.vue' }).warnings;
18
+ }
19
+
20
+ // ---------------------------------------------------------------------------
21
+ // Auto-add :interactive="true" for chips with click handlers
22
+ // ---------------------------------------------------------------------------
23
+
24
+ describe('quoted value false-positive prevention', () => {
25
+ it('does not false-positive on @click inside a quoted attribute value', () => {
26
+ const input = '<dt-chip :title=" @click is cool">Label</dt-chip>';
27
+ assert.equal(run(input), input);
28
+ assert.equal(warnings(input).length, 1);
29
+ });
30
+
31
+ it('does not false-positive on interactive inside a quoted attribute value', () => {
32
+ // HAS_INTERACTIVE_RE must not fire on this — chip still needs fixing
33
+ const input = '<dt-chip :aria-label="set interactive prop" @click="go">Label</dt-chip>';
34
+ const expected = '<dt-chip :interactive="true" :aria-label="set interactive prop" @click="go">Label</dt-chip>';
35
+ assert.equal(run(input), expected);
36
+ });
37
+
38
+ it('real :interactive prop still prevents insertion', () => {
39
+ const input = '<dt-chip :interactive="false" @click="go">Label</dt-chip>';
40
+ assert.equal(run(input), input);
41
+ });
42
+ });
43
+
44
+ describe('quote-aware attribute parsing — > inside quoted value', () => {
45
+ it('handles > inside a quoted attribute value before @click', () => {
46
+ const input = '<dt-chip :class="a > b" @click="onClick">Label</dt-chip>';
47
+ const expected = '<dt-chip :interactive="true" :class="a > b" @click="onClick">Label</dt-chip>';
48
+ assert.equal(run(input), expected);
49
+ });
50
+
51
+ it('does not warn when @click is present but attrs contain quoted >', () => {
52
+ const input = '<dt-chip :class="a > b" @click="onClick">Label</dt-chip>';
53
+ assert.equal(warnings(input).length, 0);
54
+ });
55
+
56
+ it('handles > inside a single-quoted attribute value', () => {
57
+ const input = '<dt-chip :title="x > y" @click="onClick">Label</dt-chip>';
58
+ const expected = '<dt-chip :interactive="true" :title="x > y" @click="onClick">Label</dt-chip>';
59
+ assert.equal(run(input), expected);
60
+ });
61
+ });
62
+
63
+ describe('chips with @click — auto-add :interactive="true"', () => {
64
+ const cases = [
65
+ [
66
+ 'single-line chip with @click',
67
+ '<dt-chip @click="handleClick">Label</dt-chip>',
68
+ '<dt-chip :interactive="true" @click="handleClick">Label</dt-chip>',
69
+ ],
70
+ [
71
+ 'chip with @click.stop modifier',
72
+ '<dt-chip @click.stop="handleClick">Label</dt-chip>',
73
+ '<dt-chip :interactive="true" @click.stop="handleClick">Label</dt-chip>',
74
+ ],
75
+ [
76
+ 'chip with @click.prevent modifier',
77
+ '<dt-chip @click.prevent="handleClick">Label</dt-chip>',
78
+ '<dt-chip :interactive="true" @click.prevent="handleClick">Label</dt-chip>',
79
+ ],
80
+ [
81
+ 'chip with v-on:click',
82
+ '<dt-chip v-on:click="handleClick">Label</dt-chip>',
83
+ '<dt-chip :interactive="true" v-on:click="handleClick">Label</dt-chip>',
84
+ ],
85
+ [
86
+ 'chip with other props and @click',
87
+ '<dt-chip :size="200" :disabled="isDisabled" @click="handleClick">Label</dt-chip>',
88
+ '<dt-chip :interactive="true" :size="200" :disabled="isDisabled" @click="handleClick">Label</dt-chip>',
89
+ ],
90
+ [
91
+ 'self-closing chip with @click',
92
+ '<dt-chip @click="handleClick" />',
93
+ '<dt-chip :interactive="true" @click="handleClick" />',
94
+ ],
95
+ [
96
+ 'PascalCase DtChip with @click',
97
+ '<DtChip @click="handleClick">Label</DtChip>',
98
+ '<DtChip :interactive="true" @click="handleClick">Label</DtChip>',
99
+ ],
100
+ ];
101
+
102
+ for (const [label, input, expected] of cases) {
103
+ it(label, () => {
104
+ assert.equal(run(input), expected);
105
+ });
106
+ }
107
+ });
108
+
109
+ describe('chips with v-on object binding — auto-add :interactive="true"', () => {
110
+ const cases = [
111
+ [
112
+ 'chip with v-on object binding (double quotes)',
113
+ '<dt-chip v-on="chipListeners">Label</dt-chip>',
114
+ '<dt-chip :interactive="true" v-on="chipListeners">Label</dt-chip>',
115
+ ],
116
+ [
117
+ 'chip with v-on object binding (single quotes)',
118
+ '<dt-chip v-on=\'chipListeners\'>Label</dt-chip>',
119
+ '<dt-chip :interactive="true" v-on=\'chipListeners\'>Label</dt-chip>',
120
+ ],
121
+ ];
122
+
123
+ for (const [label, input, expected] of cases) {
124
+ it(label, () => {
125
+ assert.equal(run(input), expected);
126
+ });
127
+ }
128
+ });
129
+
130
+ // ---------------------------------------------------------------------------
131
+ // Skip chips that already have the interactive prop
132
+ // ---------------------------------------------------------------------------
133
+
134
+ describe('chips that already have interactive prop — no change', () => {
135
+ const cases = [
136
+ [
137
+ 'already has :interactive="true"',
138
+ '<dt-chip :interactive="true" @click="handleClick">Label</dt-chip>',
139
+ ],
140
+ [
141
+ 'already has :interactive="false"',
142
+ '<dt-chip :interactive="false">Label</dt-chip>',
143
+ ],
144
+ [
145
+ 'already has plain interactive',
146
+ '<dt-chip interactive>Label</dt-chip>',
147
+ ],
148
+ [
149
+ 'already has v-bind:interactive',
150
+ '<dt-chip v-bind:interactive="isInteractive">Label</dt-chip>',
151
+ ],
152
+ ];
153
+
154
+ for (const [label, input] of cases) {
155
+ it(label, () => {
156
+ assert.equal(run(input), input);
157
+ });
158
+ }
159
+ });
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // Chips with no click handler — warn, no change
163
+ // ---------------------------------------------------------------------------
164
+
165
+ describe('chips with no click handler — no change, emit warning', () => {
166
+ it('display-only chip produces no output change', () => {
167
+ const input = '<dt-chip>Label</dt-chip>';
168
+ assert.equal(run(input), input);
169
+ });
170
+
171
+ it('display-only chip emits a warning', () => {
172
+ const input = '<dt-chip>Label</dt-chip>';
173
+ assert.equal(warnings(input).length, 1);
174
+ });
175
+
176
+ it('warning message mentions the file path', () => {
177
+ const { warnings: w } = transformContent('<dt-chip>Label</dt-chip>', { filePath: 'src/MyComponent.vue' });
178
+ assert.ok(w[0].includes('src/MyComponent.vue'));
179
+ });
180
+
181
+ it('chip with @close only (no @click) emits warning, no auto-change', () => {
182
+ const input = '<dt-chip @close="onRemove">Label</dt-chip>';
183
+ assert.equal(run(input), input);
184
+ assert.equal(warnings(input).length, 1);
185
+ });
186
+ });
187
+
188
+ // ---------------------------------------------------------------------------
189
+ // Multiple chips in one file
190
+ // ---------------------------------------------------------------------------
191
+
192
+ describe('multiple chips in one file', () => {
193
+ it('adds :interactive to the clickable chip only', () => {
194
+ const input = [
195
+ '<dt-chip @click="onClick">Clickable</dt-chip>',
196
+ '<dt-chip>Display</dt-chip>',
197
+ ].join('\n');
198
+ const expected = [
199
+ '<dt-chip :interactive="true" @click="onClick">Clickable</dt-chip>',
200
+ '<dt-chip>Display</dt-chip>',
201
+ ].join('\n');
202
+ assert.equal(run(input), expected);
203
+ });
204
+
205
+ it('warns once per display-only chip', () => {
206
+ const input = [
207
+ '<dt-chip>Label A</dt-chip>',
208
+ '<dt-chip>Label B</dt-chip>',
209
+ ].join('\n');
210
+ assert.equal(warnings(input).length, 2);
211
+ });
212
+ });
213
+
214
+ // ---------------------------------------------------------------------------
215
+ // Inert content masking — should not match chips in comments or script
216
+ // ---------------------------------------------------------------------------
217
+
218
+ describe('inert content masking', () => {
219
+ it('does not transform chip inside HTML comment', () => {
220
+ const input = '<!-- <dt-chip @click="x">hidden</dt-chip> -->';
221
+ assert.equal(run(input), input);
222
+ });
223
+
224
+ it('does not transform chip inside <script>', () => {
225
+ const input = '<script>\nconst example = `<dt-chip @click="x">Label</dt-chip>`;\n</script>';
226
+ assert.equal(run(input), input);
227
+ });
228
+ });
229
+
230
+ // ---------------------------------------------------------------------------
231
+ // Fast path — no-op on files with no dt-chip reference
232
+ // ---------------------------------------------------------------------------
233
+
234
+ describe('fast path', () => {
235
+ it('returns unchanged content when no dt-chip present', () => {
236
+ const input = '<dt-button @click="x">Click</dt-button>';
237
+ assert.equal(run(input), input);
238
+ });
239
+
240
+ it('emits no warnings when no dt-chip present', () => {
241
+ const input = '<dt-button @click="x">Click</dt-button>';
242
+ assert.equal(warnings(input).length, 0);
243
+ });
244
+ });