@jackuait/blok 0.3.1-beta.3 → 0.3.1-beta.7

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/README.md CHANGED
@@ -44,10 +44,13 @@ Run the codemod to automatically update your codebase:
44
44
 
45
45
  ```bash
46
46
  # Preview changes (recommended first)
47
- npx blok-codemod ./src --dry-run
47
+ npx migrate-from-editorjs ./src --dry-run
48
48
 
49
49
  # Apply changes
50
- npx blok-codemod ./src
50
+ npx migrate-from-editorjs ./src
51
+
52
+ # Process the entire project
53
+ npx migrate-from-editorjs .
51
54
  ```
52
55
 
53
56
  The codemod handles:
package/codemod/README.md CHANGED
@@ -8,16 +8,16 @@ Automatically migrate your codebase from EditorJS to Blok.
8
8
 
9
9
  ```bash
10
10
  # Dry run (preview changes without modifying files)
11
- npx blok-codemod ./src --dry-run
11
+ npx @jackuait/migrate-from-editorjs ./src --dry-run
12
12
 
13
13
  # Apply changes
14
- npx blok-codemod ./src
14
+ npx @jackuait/migrate-from-editorjs ./src
15
15
 
16
16
  # Process entire project
17
- npx blok-codemod .
17
+ npx @jackuait/migrate-from-editorjs .
18
18
 
19
19
  # Verbose output
20
- npx blok-codemod ./src --verbose
20
+ npx @jackuait/migrate-from-editorjs ./src --verbose
21
21
  ```
22
22
 
23
23
  ## What It Does
@@ -7,7 +7,7 @@
7
7
  * It transforms imports, class names, selectors, data attributes, and text references.
8
8
  *
9
9
  * Usage:
10
- * npx blok-codemod [path] [options]
10
+ * npx migrate-from-editorjs [path] [options]
11
11
  *
12
12
  * Options:
13
13
  * --dry-run Show changes without modifying files
@@ -15,9 +15,9 @@
15
15
  * --help Show help
16
16
  *
17
17
  * Examples:
18
- * npx blok-codemod ./src
19
- * npx blok-codemod ./src --dry-run
20
- * npx blok-codemod .
18
+ * npx migrate-from-editorjs ./src
19
+ * npx migrate-from-editorjs ./src --dry-run
20
+ * npx migrate-from-editorjs .
21
21
  */
22
22
 
23
23
  const fs = require('fs');
@@ -71,23 +71,99 @@ const CLASS_NAME_TRANSFORMS = [
71
71
  ];
72
72
 
73
73
  // CSS class transformations
74
+ // Handles both with dot (.ce-block) and without dot (ce-block) patterns
74
75
  const CSS_CLASS_TRANSFORMS = [
75
- // Editor wrapper classes
76
- { pattern: /\.codex-editor(?![\w-])/g, replacement: '.blok-editor' },
77
- { pattern: /\.codex-editor--narrow/g, replacement: '.blok-editor--narrow' },
78
- { pattern: /\.codex-editor--rtl/g, replacement: '.blok-editor--rtl' },
79
- // CE prefix classes (commonly used)
80
- { pattern: /\.ce-block(?![\w-])/g, replacement: '[data-blok-testid="block-wrapper"]' },
81
- { pattern: /\.ce-block--selected/g, replacement: '[data-blok-selected="true"]' },
82
- { pattern: /\.ce-block--stretched/g, replacement: '[data-blok-stretched="true"]' },
83
- { pattern: /\.ce-block__content/g, replacement: '[data-blok-testid="block-content"]' },
84
- { pattern: /\.ce-toolbar(?![\w-])/g, replacement: '[data-blok-testid="toolbar"]' },
85
- { pattern: /\.ce-toolbar__plus/g, replacement: '[data-blok-testid="plus-button"]' },
86
- { pattern: /\.ce-toolbar__settings-btn/g, replacement: '[data-blok-testid="settings-toggler"]' },
87
- { pattern: /\.ce-toolbar__actions/g, replacement: '[data-blok-testid="toolbar-actions"]' },
88
- { pattern: /\.ce-inline-toolbar/g, replacement: '[data-blok-testid="inline-toolbar"]' },
89
- { pattern: /\.ce-popover(?![\w-])/g, replacement: '[data-blok-testid="popover-container"]' },
90
- { pattern: /\.ce-popover-item/g, replacement: '[data-blok-testid="popover-item"]' },
76
+ // Editor wrapper classes (codex-editor)
77
+ { pattern: /\.codex-editor__redactor(?![\w-])/g, replacement: '[data-blok-redactor]' },
78
+ { pattern: /\.codex-editor--narrow(?![\w-])/g, replacement: '[data-blok-narrow="true"]' },
79
+ { pattern: /\.codex-editor--rtl(?![\w-])/g, replacement: '[data-blok-rtl="true"]' },
80
+ { pattern: /\.codex-editor(?![\w-])/g, replacement: '[data-blok-editor]' },
81
+ // Without dot prefix (for string literals, classList operations)
82
+ { pattern: /(['"`])codex-editor__redactor(['"`])/g, replacement: '$1data-blok-redactor$2' },
83
+ { pattern: /(['"`])codex-editor--narrow(['"`])/g, replacement: '$1data-blok-narrow$2' },
84
+ { pattern: /(['"`])codex-editor--rtl(['"`])/g, replacement: '$1data-blok-rtl$2' },
85
+ { pattern: /(['"`])codex-editor(['"`])/g, replacement: '$1data-blok-editor$2' },
86
+
87
+ // Block classes (ce-block)
88
+ { pattern: /\.ce-block--selected(?![\w-])/g, replacement: '[data-blok-selected="true"]' },
89
+ { pattern: /\.ce-block--stretched(?![\w-])/g, replacement: '[data-blok-stretched="true"]' },
90
+ { pattern: /\.ce-block--focused(?![\w-])/g, replacement: '[data-blok-focused="true"]' },
91
+ { pattern: /\.ce-block__content(?![\w-])/g, replacement: '[data-blok-element-content]' },
92
+ { pattern: /\.ce-block(?![\w-])/g, replacement: '[data-blok-element]' },
93
+ // Without dot prefix
94
+ { pattern: /(['"`])ce-block--selected(['"`])/g, replacement: '$1data-blok-selected$2' },
95
+ { pattern: /(['"`])ce-block--stretched(['"`])/g, replacement: '$1data-blok-stretched$2' },
96
+ { pattern: /(['"`])ce-block--focused(['"`])/g, replacement: '$1data-blok-focused$2' },
97
+ { pattern: /(['"`])ce-block__content(['"`])/g, replacement: '$1data-blok-element-content$2' },
98
+ { pattern: /(['"`])ce-block(['"`])/g, replacement: '$1data-blok-element$2' },
99
+
100
+ // Toolbar classes (ce-toolbar)
101
+ { pattern: /\.ce-toolbar__plus(?![\w-])/g, replacement: '[data-blok-testid="plus-button"]' },
102
+ { pattern: /\.ce-toolbar__settings-btn(?![\w-])/g, replacement: '[data-blok-settings-toggler]' },
103
+ { pattern: /\.ce-toolbar__actions(?![\w-])/g, replacement: '[data-blok-testid="toolbar-actions"]' },
104
+ { pattern: /\.ce-toolbar(?![\w-])/g, replacement: '[data-blok-toolbar]' },
105
+ // Without dot prefix
106
+ { pattern: /(['"`])ce-toolbar__plus(['"`])/g, replacement: '$1data-blok-testid="plus-button"$2' },
107
+ { pattern: /(['"`])ce-toolbar__settings-btn(['"`])/g, replacement: '$1data-blok-settings-toggler$2' },
108
+ { pattern: /(['"`])ce-toolbar__actions(['"`])/g, replacement: '$1data-blok-testid="toolbar-actions"$2' },
109
+ { pattern: /(['"`])ce-toolbar(['"`])/g, replacement: '$1data-blok-toolbar$2' },
110
+
111
+ // Inline toolbar classes (ce-inline-toolbar, ce-inline-tool)
112
+ { pattern: /\.ce-inline-tool--link(?![\w-])/g, replacement: '[data-blok-testid="inline-tool-link"]' },
113
+ { pattern: /\.ce-inline-tool--bold(?![\w-])/g, replacement: '[data-blok-testid="inline-tool-bold"]' },
114
+ { pattern: /\.ce-inline-tool--italic(?![\w-])/g, replacement: '[data-blok-testid="inline-tool-italic"]' },
115
+ { pattern: /\.ce-inline-tool(?![\w-])/g, replacement: '[data-blok-testid="inline-tool"]' },
116
+ { pattern: /\.ce-inline-toolbar(?![\w-])/g, replacement: '[data-blok-testid="inline-toolbar"]' },
117
+ // Without dot prefix
118
+ { pattern: /(['"`])ce-inline-tool--link(['"`])/g, replacement: '$1data-blok-testid="inline-tool-link"$2' },
119
+ { pattern: /(['"`])ce-inline-tool--bold(['"`])/g, replacement: '$1data-blok-testid="inline-tool-bold"$2' },
120
+ { pattern: /(['"`])ce-inline-tool--italic(['"`])/g, replacement: '$1data-blok-testid="inline-tool-italic"$2' },
121
+ { pattern: /(['"`])ce-inline-tool(['"`])/g, replacement: '$1data-blok-testid="inline-tool"$2' },
122
+ { pattern: /(['"`])ce-inline-toolbar(['"`])/g, replacement: '$1data-blok-testid="inline-toolbar"$2' },
123
+
124
+ // Popover classes (ce-popover)
125
+ { pattern: /\.ce-popover--opened(?![\w-])/g, replacement: '[data-blok-popover][data-blok-opened="true"]' },
126
+ { pattern: /\.ce-popover__container(?![\w-])/g, replacement: '[data-blok-popover-container]' },
127
+ { pattern: /\.ce-popover-item--focused(?![\w-])/g, replacement: '[data-blok-focused="true"]' },
128
+ { pattern: /\.ce-popover-item(?![\w-])/g, replacement: '[data-blok-testid="popover-item"]' },
129
+ { pattern: /\.ce-popover(?![\w-])/g, replacement: '[data-blok-popover]' },
130
+ // Without dot prefix
131
+ { pattern: /(['"`])ce-popover--opened(['"`])/g, replacement: '$1data-blok-popover$2' },
132
+ { pattern: /(['"`])ce-popover__container(['"`])/g, replacement: '$1data-blok-popover-container$2' },
133
+ { pattern: /(['"`])ce-popover-item--focused(['"`])/g, replacement: '$1data-blok-focused$2' },
134
+ { pattern: /(['"`])ce-popover-item(['"`])/g, replacement: '$1data-blok-testid="popover-item"$2' },
135
+ { pattern: /(['"`])ce-popover(['"`])/g, replacement: '$1data-blok-popover$2' },
136
+
137
+ // Tool-specific classes (ce-paragraph, ce-header)
138
+ { pattern: /\.ce-paragraph(?![\w-])/g, replacement: '[data-blok-tool="paragraph"]' },
139
+ { pattern: /\.ce-header(?![\w-])/g, replacement: '[data-blok-tool="header"]' },
140
+ // Without dot prefix
141
+ { pattern: /(['"`])ce-paragraph(['"`])/g, replacement: '$1data-blok-tool="paragraph"$2' },
142
+ { pattern: /(['"`])ce-header(['"`])/g, replacement: '$1data-blok-tool="header"$2' },
143
+
144
+ // Conversion toolbar
145
+ { pattern: /\.ce-conversion-toolbar(?![\w-])/g, replacement: '[data-blok-testid="conversion-toolbar"]' },
146
+ { pattern: /\.ce-conversion-tool(?![\w-])/g, replacement: '[data-blok-testid="conversion-tool"]' },
147
+ { pattern: /(['"`])ce-conversion-toolbar(['"`])/g, replacement: '$1data-blok-testid="conversion-toolbar"$2' },
148
+ { pattern: /(['"`])ce-conversion-tool(['"`])/g, replacement: '$1data-blok-testid="conversion-tool"$2' },
149
+
150
+ // Settings and tune classes
151
+ { pattern: /\.ce-settings(?![\w-])/g, replacement: '[data-blok-testid="block-settings"]' },
152
+ { pattern: /\.ce-tune(?![\w-])/g, replacement: '[data-blok-testid="block-tune"]' },
153
+ { pattern: /(['"`])ce-settings(['"`])/g, replacement: '$1data-blok-testid="block-settings"$2' },
154
+ { pattern: /(['"`])ce-tune(['"`])/g, replacement: '$1data-blok-testid="block-tune"$2' },
155
+
156
+ // Stub block
157
+ { pattern: /\.ce-stub(?![\w-])/g, replacement: '[data-blok-stub]' },
158
+ { pattern: /(['"`])ce-stub(['"`])/g, replacement: '$1data-blok-stub$2' },
159
+
160
+ // Drag and drop
161
+ { pattern: /\.ce-drag-handle(?![\w-])/g, replacement: '[data-blok-drag-handle]' },
162
+ { pattern: /(['"`])ce-drag-handle(['"`])/g, replacement: '$1data-blok-drag-handle$2' },
163
+
164
+ // Additional state classes
165
+ { pattern: /\.ce-ragged-right(?![\w-])/g, replacement: '[data-blok-ragged-right="true"]' },
166
+ { pattern: /(['"`])ce-ragged-right(['"`])/g, replacement: '$1data-blok-ragged-right$2' },
91
167
  ];
92
168
 
93
169
  // Data attribute transformations
@@ -119,6 +195,9 @@ const HOLDER_TRANSFORMS = [
119
195
  { pattern: /getElementById\s*\(\s*['"]editorjs['"]\s*\)/g, replacement: "getElementById('blok')" },
120
196
  ];
121
197
 
198
+ // Bundled tools - add new tools here as they are bundled with Blok
199
+ const BUNDLED_TOOLS = ['Header', 'Paragraph'];
200
+
122
201
  // Tool configuration transformations
123
202
  const TOOL_CONFIG_TRANSFORMS = [
124
203
  // Handle class property syntax
@@ -196,6 +275,108 @@ function applyTransforms(content, transforms, fileName) {
196
275
  return { result, changes };
197
276
  }
198
277
 
278
+ /**
279
+ * Ensures that Blok is properly imported when bundled tools (Blok.Header, Blok.Paragraph, etc.) are used.
280
+ * This function checks if the content uses any Blok.* tool references and ensures there's a proper import.
281
+ *
282
+ * Handles the following scenarios:
283
+ * 1. No existing @jackuait/blok import -> adds `import Blok from '@jackuait/blok'`
284
+ * 2. Named imports only (e.g., `import { BlokConfig } from '@jackuait/blok'`) -> adds Blok default import
285
+ * 3. Default import with different name -> adds Blok to named imports
286
+ * 4. Already has Blok default import -> no changes needed
287
+ */
288
+ function ensureBlokImport(content) {
289
+ // Check if content uses any Blok.* tool (e.g., Blok.Header, Blok.Paragraph)
290
+ const blokToolPattern = new RegExp(`Blok\\.(${BUNDLED_TOOLS.join('|')})`, 'g');
291
+ const usesBlokTools = blokToolPattern.test(content);
292
+
293
+ if (!usesBlokTools) {
294
+ return { result: content, changed: false };
295
+ }
296
+
297
+ // Check if Blok is already available as a default import
298
+ // Matches: import Blok from '@jackuait/blok' or import Blok, { ... } from '@jackuait/blok'
299
+ const hasBlokDefaultImport = /import\s+Blok\s*(?:,\s*\{[^}]*\}\s*)?from\s*['"]@jackuait\/blok['"]/.test(content);
300
+
301
+ if (hasBlokDefaultImport) {
302
+ return { result: content, changed: false };
303
+ }
304
+
305
+ // Check for existing @jackuait/blok import patterns
306
+ const namedOnlyImportPattern = /import\s*\{([^}]+)\}\s*from\s*['"]@jackuait\/blok['"];?/;
307
+ const defaultWithNamedPattern = /import\s+(\w+)\s*,\s*\{([^}]+)\}\s*from\s*['"]@jackuait\/blok['"];?/;
308
+ const defaultOnlyPattern = /import\s+(\w+)\s+from\s*['"]@jackuait\/blok['"];?/;
309
+
310
+ let result = content;
311
+
312
+ // Case 1: Named imports only -> add Blok default import
313
+ // e.g., `import { BlokConfig } from '@jackuait/blok'` -> `import Blok, { BlokConfig } from '@jackuait/blok'`
314
+ const namedOnlyMatch = content.match(namedOnlyImportPattern);
315
+ if (namedOnlyMatch) {
316
+ const namedImports = namedOnlyMatch[1];
317
+ result = content.replace(
318
+ namedOnlyImportPattern,
319
+ `import Blok, {${namedImports}} from '@jackuait/blok';`
320
+ );
321
+ return { result, changed: true };
322
+ }
323
+
324
+ // Case 2: Default import with different name + named imports -> add Blok to named imports
325
+ // e.g., `import Editor, { BlokConfig } from '@jackuait/blok'` -> `import Editor, { Blok, BlokConfig } from '@jackuait/blok'`
326
+ const defaultWithNamedMatch = content.match(defaultWithNamedPattern);
327
+ if (defaultWithNamedMatch) {
328
+ const defaultName = defaultWithNamedMatch[1];
329
+ const namedImports = defaultWithNamedMatch[2];
330
+ // Check if Blok is already in named imports
331
+ if (!/\bBlok\b/.test(namedImports)) {
332
+ result = content.replace(
333
+ defaultWithNamedPattern,
334
+ `import ${defaultName}, { Blok, ${namedImports.trim()} } from '@jackuait/blok';`
335
+ );
336
+ return { result, changed: true };
337
+ }
338
+ return { result: content, changed: false };
339
+ }
340
+
341
+ // Case 3: Default import only with different name -> add Blok to named imports
342
+ // e.g., `import Editor from '@jackuait/blok'` -> `import Editor, { Blok } from '@jackuait/blok'`
343
+ const defaultOnlyMatch = content.match(defaultOnlyPattern);
344
+ if (defaultOnlyMatch) {
345
+ const defaultName = defaultOnlyMatch[1];
346
+ if (defaultName !== 'Blok') {
347
+ result = content.replace(
348
+ defaultOnlyPattern,
349
+ `import ${defaultName}, { Blok } from '@jackuait/blok';`
350
+ );
351
+ return { result, changed: true };
352
+ }
353
+ return { result: content, changed: false };
354
+ }
355
+
356
+ // Case 4: No @jackuait/blok import at all -> add new import at the top (after any existing imports)
357
+ // Find the last import statement to insert after it
358
+ const importStatements = content.match(/^import\s+.+from\s+['"][^'"]+['"];?\s*$/gm);
359
+ if (importStatements && importStatements.length > 0) {
360
+ const lastImport = importStatements[importStatements.length - 1];
361
+ const lastImportIndex = content.lastIndexOf(lastImport);
362
+ const insertPosition = lastImportIndex + lastImport.length;
363
+ result =
364
+ content.slice(0, insertPosition) +
365
+ "\nimport Blok from '@jackuait/blok';" +
366
+ content.slice(insertPosition);
367
+ } else {
368
+ // No imports found, add at the very beginning (after shebang if present)
369
+ const shebangMatch = content.match(/^#!.*\n/);
370
+ if (shebangMatch) {
371
+ result = shebangMatch[0] + "import Blok from '@jackuait/blok';\n" + content.slice(shebangMatch[0].length);
372
+ } else {
373
+ result = "import Blok from '@jackuait/blok';\n" + content;
374
+ }
375
+ }
376
+
377
+ return { result, changed: true };
378
+ }
379
+
199
380
  function transformFile(filePath, dryRun = false) {
200
381
  const content = fs.readFileSync(filePath, 'utf8');
201
382
  let transformed = content;
@@ -262,6 +443,15 @@ function transformFile(filePath, dryRun = false) {
262
443
  allChanges.push(...changes.map((c) => ({ ...c, category: 'tool-config' })));
263
444
  }
264
445
 
446
+ // Ensure Blok is imported if bundled tools are used (JS/TS only)
447
+ if (isJsFile) {
448
+ const { result, changed } = ensureBlokImport(transformed);
449
+ if (changed) {
450
+ transformed = result;
451
+ allChanges.push({ category: 'imports', pattern: 'ensureBlokImport', count: 1, note: 'Added Blok import for bundled tools' });
452
+ }
453
+ }
454
+
265
455
  // Apply text transforms (JS/TS/HTML) - replace "EditorJS" with "Blok"
266
456
  if (isJsFile || isHtmlFile) {
267
457
  const { result, changes } = applyTransforms(transformed, TEXT_TRANSFORMS, filePath);
@@ -339,7 +529,7 @@ function printHelp() {
339
529
  EditorJS to Blok Codemod
340
530
 
341
531
  Usage:
342
- npx blok-codemod [path] [options]
532
+ npx migrate-from-editorjs [path] [options]
343
533
 
344
534
  Arguments:
345
535
  path Directory or file to transform (default: current directory)
@@ -350,19 +540,26 @@ Options:
350
540
  --help Show this help message
351
541
 
352
542
  Examples:
353
- npx blok-codemod ./src
354
- npx blok-codemod ./src --dry-run
355
- npx blok-codemod . --verbose
543
+ npx migrate-from-editorjs ./src
544
+ npx migrate-from-editorjs ./src --dry-run
545
+ npx migrate-from-editorjs . --verbose
356
546
 
357
547
  What this codemod does:
358
548
  • Transforms EditorJS imports to Blok imports
359
549
  • Updates type names (EditorConfig → BlokConfig)
360
550
  • Replaces 'new EditorJS()' with 'new Blok()'
361
- • Converts CSS selectors (.ce-* [data-blok-*])
551
+ • Converts CSS class selectors to data attributes:
552
+ - .codex-editor* → [data-blok-editor], [data-blok-redactor], etc.
553
+ - .ce-block* → [data-blok-element], [data-blok-selected], etc.
554
+ - .ce-toolbar* → [data-blok-toolbar], [data-blok-settings-toggler], etc.
555
+ - .ce-inline-toolbar, .ce-inline-tool* → [data-blok-testid="inline-*"]
556
+ - .ce-popover* → [data-blok-popover], [data-blok-popover-container], etc.
557
+ - .ce-paragraph, .ce-header → [data-blok-tool="paragraph|header"]
362
558
  • Updates data attributes (data-id → data-blok-id)
363
559
  • Changes default holder from 'editorjs' to 'blok'
364
560
  • Updates package.json dependencies
365
561
  • Converts bundled tool imports (Header, Paragraph)
562
+ • Ensures Blok is imported when using bundled tools (Blok.Header, etc.)
366
563
 
367
564
  Note: After running, you may need to manually:
368
565
  • Update any custom tool implementations
@@ -484,6 +681,8 @@ module.exports = {
484
681
  transformFile,
485
682
  updatePackageJson,
486
683
  applyTransforms,
684
+ ensureBlokImport,
685
+ BUNDLED_TOOLS,
487
686
  IMPORT_TRANSFORMS,
488
687
  TYPE_TRANSFORMS,
489
688
  CLASS_NAME_TRANSFORMS,
@@ -1,11 +1,10 @@
1
1
  {
2
- "name": "@jackuait/blok-codemod",
2
+ "name": "@jackuait/migrate-from-editorjs",
3
3
  "version": "1.0.0",
4
4
  "description": "Codemod to migrate from EditorJS to Blok",
5
5
  "main": "migrate-editorjs-to-blok.js",
6
6
  "bin": {
7
- "blok-codemod": "./migrate-editorjs-to-blok.js",
8
- "migrate-editorjs-to-blok": "./migrate-editorjs-to-blok.js"
7
+ "migrate-from-editorjs": "./migrate-editorjs-to-blok.js"
9
8
  },
10
9
  "keywords": [
11
10
  "blok",
package/codemod/test.js CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  const {
6
6
  applyTransforms,
7
+ ensureBlokImport,
8
+ BUNDLED_TOOLS,
7
9
  IMPORT_TRANSFORMS,
8
10
  TYPE_TRANSFORMS,
9
11
  CLASS_NAME_TRANSFORMS,
@@ -113,19 +115,25 @@ console.log('\n🎨 CSS Class Transformations\n');
113
115
  test('transforms .codex-editor class', () => {
114
116
  const input = `.codex-editor { color: red; }`;
115
117
  const { result } = applyTransforms(input, CSS_CLASS_TRANSFORMS);
116
- assertEqual(result, `.blok-editor { color: red; }`);
118
+ assertEqual(result, `[data-blok-editor] { color: red; }`);
117
119
  });
118
120
 
119
121
  test('transforms .codex-editor--narrow modifier', () => {
120
122
  const input = `.codex-editor--narrow { width: 100%; }`;
121
123
  const { result } = applyTransforms(input, CSS_CLASS_TRANSFORMS);
122
- assertEqual(result, `.blok-editor--narrow { width: 100%; }`);
124
+ assertEqual(result, `[data-blok-narrow="true"] { width: 100%; }`);
125
+ });
126
+
127
+ test('transforms .codex-editor__redactor class', () => {
128
+ const input = `.codex-editor__redactor { padding: 20px; }`;
129
+ const { result } = applyTransforms(input, CSS_CLASS_TRANSFORMS);
130
+ assertEqual(result, `[data-blok-redactor] { padding: 20px; }`);
123
131
  });
124
132
 
125
133
  test('transforms .ce-block class', () => {
126
134
  const input = `.ce-block { margin: 10px; }`;
127
135
  const { result } = applyTransforms(input, CSS_CLASS_TRANSFORMS);
128
- assertEqual(result, `[data-blok-testid="block-wrapper"] { margin: 10px; }`);
136
+ assertEqual(result, `[data-blok-element] { margin: 10px; }`);
129
137
  });
130
138
 
131
139
  test('transforms .ce-block--selected class', () => {
@@ -137,7 +145,7 @@ test('transforms .ce-block--selected class', () => {
137
145
  test('transforms .ce-toolbar class', () => {
138
146
  const input = `document.querySelector('.ce-toolbar')`;
139
147
  const { result } = applyTransforms(input, CSS_CLASS_TRANSFORMS);
140
- assertEqual(result, `document.querySelector('[data-blok-testid="toolbar"]')`);
148
+ assertEqual(result, `document.querySelector('[data-blok-toolbar]')`);
141
149
  });
142
150
 
143
151
  test('transforms .ce-inline-toolbar class', () => {
@@ -146,6 +154,66 @@ test('transforms .ce-inline-toolbar class', () => {
146
154
  assertEqual(result, `[data-blok-testid="inline-toolbar"] { display: flex; }`);
147
155
  });
148
156
 
157
+ test('transforms .ce-paragraph class', () => {
158
+ const input = `.ce-paragraph { line-height: 1.6; }`;
159
+ const { result } = applyTransforms(input, CSS_CLASS_TRANSFORMS);
160
+ assertEqual(result, `[data-blok-tool="paragraph"] { line-height: 1.6; }`);
161
+ });
162
+
163
+ test('transforms .ce-header class', () => {
164
+ const input = `.ce-header { font-weight: bold; }`;
165
+ const { result } = applyTransforms(input, CSS_CLASS_TRANSFORMS);
166
+ assertEqual(result, `[data-blok-tool="header"] { font-weight: bold; }`);
167
+ });
168
+
169
+ test('transforms .ce-inline-tool--link class', () => {
170
+ const input = `.ce-inline-tool--link { color: blue; }`;
171
+ const { result } = applyTransforms(input, CSS_CLASS_TRANSFORMS);
172
+ assertEqual(result, `[data-blok-testid="inline-tool-link"] { color: blue; }`);
173
+ });
174
+
175
+ test('transforms .ce-inline-tool--bold class', () => {
176
+ const input = `.ce-inline-tool--bold { font-weight: bold; }`;
177
+ const { result } = applyTransforms(input, CSS_CLASS_TRANSFORMS);
178
+ assertEqual(result, `[data-blok-testid="inline-tool-bold"] { font-weight: bold; }`);
179
+ });
180
+
181
+ test('transforms .ce-inline-tool--italic class', () => {
182
+ const input = `.ce-inline-tool--italic { font-style: italic; }`;
183
+ const { result } = applyTransforms(input, CSS_CLASS_TRANSFORMS);
184
+ assertEqual(result, `[data-blok-testid="inline-tool-italic"] { font-style: italic; }`);
185
+ });
186
+
187
+ test('transforms .ce-popover class', () => {
188
+ const input = `.ce-popover { position: absolute; }`;
189
+ const { result } = applyTransforms(input, CSS_CLASS_TRANSFORMS);
190
+ assertEqual(result, `[data-blok-popover] { position: absolute; }`);
191
+ });
192
+
193
+ test('transforms .ce-popover--opened class', () => {
194
+ const input = `.ce-popover--opened { display: block; }`;
195
+ const { result } = applyTransforms(input, CSS_CLASS_TRANSFORMS);
196
+ assertEqual(result, `[data-blok-popover][data-blok-opened="true"] { display: block; }`);
197
+ });
198
+
199
+ test('transforms .ce-popover__container class', () => {
200
+ const input = `.ce-popover__container { overflow: hidden; }`;
201
+ const { result } = applyTransforms(input, CSS_CLASS_TRANSFORMS);
202
+ assertEqual(result, `[data-blok-popover-container] { overflow: hidden; }`);
203
+ });
204
+
205
+ test('transforms class names without dot prefix (string literals)', () => {
206
+ const input = `element.classList.add('ce-block');`;
207
+ const { result } = applyTransforms(input, CSS_CLASS_TRANSFORMS);
208
+ assertEqual(result, `element.classList.add('data-blok-element');`);
209
+ });
210
+
211
+ test('transforms codex-editor in string literals', () => {
212
+ const input = `const wrapper = document.querySelector("codex-editor");`;
213
+ const { result } = applyTransforms(input, CSS_CLASS_TRANSFORMS);
214
+ assertEqual(result, `const wrapper = document.querySelector("data-blok-editor");`);
215
+ });
216
+
149
217
  // ============================================================================
150
218
  // Data Attribute Tests
151
219
  // ============================================================================
@@ -291,6 +359,152 @@ test('does not transform unrelated EditorJS-like strings', () => {
291
359
  assertEqual(result, input);
292
360
  });
293
361
 
362
+ // ============================================================================
363
+ // Ensure Blok Import Tests
364
+ // ============================================================================
365
+
366
+ console.log('\n📥 Ensure Blok Import\n');
367
+
368
+ test('adds Blok import when using Blok.Header with no existing import', () => {
369
+ const input = `const editor = new Blok({
370
+ tools: { header: Blok.Header }
371
+ });`;
372
+ const { result, changed } = ensureBlokImport(input);
373
+ assertEqual(changed, true, 'Should indicate change');
374
+ assertEqual(result.includes("import Blok from '@jackuait/blok';"), true, 'Should add Blok import');
375
+ });
376
+
377
+ test('adds Blok import after existing imports', () => {
378
+ const input = `import React from 'react';
379
+ import { useState } from 'react';
380
+
381
+ const editor = new Blok({
382
+ tools: { header: Blok.Header }
383
+ });`;
384
+ const { result, changed } = ensureBlokImport(input);
385
+ assertEqual(changed, true, 'Should indicate change');
386
+ // Check that Blok import is added after existing imports
387
+ const blokImportIndex = result.indexOf("import Blok from '@jackuait/blok';");
388
+ const lastReactImportIndex = result.indexOf("import { useState } from 'react';");
389
+ assertEqual(blokImportIndex > lastReactImportIndex, true, 'Blok import should be after existing imports');
390
+ });
391
+
392
+ test('adds Blok to named-only import from @jackuait/blok', () => {
393
+ const input = `import { BlokConfig } from '@jackuait/blok';
394
+
395
+ const config: BlokConfig = {
396
+ tools: { header: Blok.Header }
397
+ };`;
398
+ const { result, changed } = ensureBlokImport(input);
399
+ assertEqual(changed, true, 'Should indicate change');
400
+ assertEqual(result.includes("import Blok, { BlokConfig } from '@jackuait/blok';"), true, 'Should add Blok default import');
401
+ });
402
+
403
+ test('adds Blok to named imports when default import has different name', () => {
404
+ const input = `import Editor, { BlokConfig } from '@jackuait/blok';
405
+
406
+ const config: BlokConfig = {
407
+ tools: { header: Blok.Header }
408
+ };`;
409
+ const { result, changed } = ensureBlokImport(input);
410
+ assertEqual(changed, true, 'Should indicate change');
411
+ assertEqual(result.includes("import Editor, { Blok, BlokConfig } from '@jackuait/blok';"), true, 'Should add Blok to named imports');
412
+ });
413
+
414
+ test('adds Blok as named import when default import has different name (no existing named imports)', () => {
415
+ const input = `import Editor from '@jackuait/blok';
416
+
417
+ const editor = new Editor({
418
+ tools: { header: Blok.Header }
419
+ });`;
420
+ const { result, changed } = ensureBlokImport(input);
421
+ assertEqual(changed, true, 'Should indicate change');
422
+ assertEqual(result.includes("import Editor, { Blok } from '@jackuait/blok';"), true, 'Should add Blok as named import');
423
+ });
424
+
425
+ test('does not modify when Blok is already default imported', () => {
426
+ const input = `import Blok from '@jackuait/blok';
427
+
428
+ const editor = new Blok({
429
+ tools: { header: Blok.Header }
430
+ });`;
431
+ const { result, changed } = ensureBlokImport(input);
432
+ assertEqual(changed, false, 'Should not indicate change');
433
+ assertEqual(result, input, 'Content should be unchanged');
434
+ });
435
+
436
+ test('does not modify when Blok is already default imported with named imports', () => {
437
+ const input = `import Blok, { BlokConfig } from '@jackuait/blok';
438
+
439
+ const editor = new Blok({
440
+ tools: { header: Blok.Header }
441
+ });`;
442
+ const { result, changed } = ensureBlokImport(input);
443
+ assertEqual(changed, false, 'Should not indicate change');
444
+ assertEqual(result, input, 'Content should be unchanged');
445
+ });
446
+
447
+ test('does not modify when no Blok tools are used', () => {
448
+ const input = `import { BlokConfig } from '@jackuait/blok';
449
+
450
+ const config: BlokConfig = {};`;
451
+ const { result, changed } = ensureBlokImport(input);
452
+ assertEqual(changed, false, 'Should not indicate change when no Blok.* tools used');
453
+ assertEqual(result, input, 'Content should be unchanged');
454
+ });
455
+
456
+ test('detects Blok.Paragraph usage', () => {
457
+ const input = `const editor = new Blok({
458
+ tools: { paragraph: Blok.Paragraph }
459
+ });`;
460
+ const { result, changed } = ensureBlokImport(input);
461
+ assertEqual(changed, true, 'Should detect Blok.Paragraph');
462
+ assertEqual(result.includes("import Blok from '@jackuait/blok';"), true, 'Should add Blok import');
463
+ });
464
+
465
+ test('handles multiple Blok tools usage', () => {
466
+ const input = `const editor = new Blok({
467
+ tools: {
468
+ header: Blok.Header,
469
+ paragraph: Blok.Paragraph
470
+ }
471
+ });`;
472
+ const { result, changed } = ensureBlokImport(input);
473
+ assertEqual(changed, true, 'Should detect multiple Blok tools');
474
+ assertEqual(result.includes("import Blok from '@jackuait/blok';"), true, 'Should add Blok import');
475
+ });
476
+
477
+ test('full migration adds Blok import for bundled tools', () => {
478
+ // This simulates a complete migration from EditorJS with bundled tools
479
+ const input = `import EditorJS from '@editorjs/editorjs';
480
+ import Header from '@editorjs/header';
481
+ import Paragraph from '@editorjs/paragraph';
482
+
483
+ const editor = new EditorJS({
484
+ holder: 'editorjs',
485
+ tools: {
486
+ header: {
487
+ class: Header,
488
+ },
489
+ paragraph: {
490
+ class: Paragraph,
491
+ },
492
+ },
493
+ });`;
494
+
495
+ let result = input;
496
+ result = applyTransforms(result, IMPORT_TRANSFORMS).result;
497
+ result = applyTransforms(result, CLASS_NAME_TRANSFORMS).result;
498
+ result = applyTransforms(result, HOLDER_TRANSFORMS).result;
499
+ result = applyTransforms(result, TOOL_CONFIG_TRANSFORMS).result;
500
+ result = ensureBlokImport(result).result;
501
+
502
+ // After transformation, should have Blok import (since original EditorJS import becomes @jackuait/blok)
503
+ assertEqual(result.includes("from '@jackuait/blok'"), true, 'Should have @jackuait/blok import');
504
+ assertEqual(result.includes('class: Blok.Header'), true, 'Should use Blok.Header');
505
+ assertEqual(result.includes('class: Blok.Paragraph'), true, 'Should use Blok.Paragraph');
506
+ });
507
+
294
508
  // ============================================================================
295
509
  // Summary
296
510
  // ============================================================================