Skip to content

📚 Lý Thuyết

Case Study: Tháng 3 năm 2026

Một ứng dụng TaskFlow có 572 backend tests đều PASS nhưng public/static/app.js chứa lỗi syntax catastrophic. Kết quả: white screen in production.

Bài học: Frontend validation không thể bỏ qua, ngay cả khi backend hoàn hảo.

7 Layers Frontend Safety

Trong cm-quality-gate, chúng ta kiểm tra 7 tầng an toàn cho code frontend:

LayerMô TảĐộ Ưu Tiên
1Syntax Validation (acorn parse)CRITICAL
2Function IntegrityRequired
3Template Safety (HTML tag matching)Required
4Asset ReferencesRequired
5Corruption Patterns (empty functions, truncation)Required
6Import/ExportRecommended
7CSS ValidationRecommended

Layer 1 - 4 Corruption Patterns (cm-test-gate)

Các mẫu lỗi phổ biến mà regex có thể phát hiện trước khi syntax parser chạy:

javascript
// Pattern 1: Single-quote wrapping template literal
const statusText = '${t("task.status.completed")}';  // WRONG!
// Regex: /=\s*'[^']*\$\{t\(/

// Pattern 2: Mismatched delimiters (t function)
const msg = t('hello `world');  // WRONG!
const msg2 = t(`hello 'world'); // WRONG!
// Regex: /t\('[^']*\`/ và /t\(\`[^']*'\)/

// Pattern 3: Broken HTML opening tags
const html = '< div class="box">Hello</ div>';  // WRONG!
// Regex: /<\s+[a-zA-Z]/

// Pattern 4: Broken closing tags
const html2 = '<div>< /div>';  // WRONG!
// Regex: /<\/\s+[a-zA-Z]/

🧪 Thực Hành

1. Bug #4 - Analyze Current Issue

Hiện tại trong public/static/app.js, tồn tại dòng:

javascript
const statusText = '${t("task.status.completed")}';

Vấn đề: Dùng single quote ' thay vì backtick ` → template literal không hoạt động, tương đương chuỗi thường "${t(...)}" (chuỗi tĩnh, không thay thế biến).

2. Create Test File: test/frontend-safety.test.ts

typescript
import { describe, it, expect, beforeAll } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import { parse } from 'acorn';

/**
 * Frontend Safety Testing - Layer 1
 * 
 * Kiểm tra 4 corruption patterns trước khi acorn parse
 * Tránh white screen in production do syntax error
 */

describe('Frontend Safety - Layer 1', () => {
  let appJsContent: string;
  let appJsPath: string;

  beforeAll(() => {
    // Load app.js từ public/static
    appJsPath = path.resolve(process.cwd(), 'public', 'static', 'app.js');
    if (fs.existsSync(appJsPath)) {
      appJsContent = fs.readFileSync(appJsPath, 'utf-8');
    } else {
      // Fallback cho test environment
      appJsContent = `
        const statusText = '\${t("task.status.completed")}';
        function initApp() {
          console.log('App initialized');
        }
        window.app = { init: initApp };
      `;
    }
  });

  describe('Pattern 1: Single-quote Template Literal Wrapper', () => {
    /**
     * ❌ PATTERN: =\s*'[^']*\$\{t\(
     * 
     * Tìm: biến gán = sau đó single quote chứa ${t(
     * Ý tưởng: template literal phải dùng backtick, không phải single quote
     */
    it('Should NOT have single quotes wrapping template with ${t(...)}', () => {
      const corruptionPattern = /=\s*'[^']*\$\{t\(/g;
      const matches = appJsContent.match(corruptionPattern);
      
      expect(matches, 'Detected Pattern 1: single-quote template literal').toBeNull();
    });

    it('Should trigger FAIL when Pattern 1 exists', () => {
      const badCode = `const statusText = '${t("task.status.completed")}';`;
      const corruptionPattern = /=\s*'[^']*\$\{t\(/g;
      const matches = badCode.match(corruptionPattern);
      
      // Verify pattern catches the error
      expect(matches).toBeTruthy();
      expect(matches?.length).toBeGreaterThan(0);
    });

    it('Should PASS when using backticks (correct syntax)', () => {
      const correctCode = `const statusText = \`\${t("task.status.completed")}\`;`;
      const corruptionPattern = /=\s*'[^']*\$\{t\(/g;
      const matches = correctCode.match(corruptionPattern);
      
      expect(matches).toBeNull();
    });
  });

  describe('Pattern 2: Mismatched Template Delimiters in t() function', () => {
    /**
     * ❌ PATTERN 1: t\('[^']*\`
     * Tìm: t('...`) — mở single quote, đóng backtick
     * 
     * ❌ PATTERN 2: t\(\`[^']*'\)
     * Tìm: t(`...') — mở backtick, đóng single quote
     */
    it('Should NOT have t() with opening single quote, closing backtick', () => {
      const pattern = /t\('[^']*\`/g;
      const matches = appJsContent.match(pattern);
      
      expect(matches, 'Pattern 2a: t(\' ... `)').toBeNull();
    });

    it('Should NOT have t() with opening backtick, closing single quote', () => {
      const pattern = /t\(\`[^']*'\)/g;
      const matches = appJsContent.match(pattern);
      
      expect(matches, 'Pattern 2b: t(` ... \')').toBeNull();
    });

    it('Should trigger FAIL when mismatched delimiters exist', () => {
      const badCode1 = `const msg = t('hello \`world');`;
      const badCode2 = `const msg2 = t(\`hello 'world');`;
      
      const pattern1 = /t\('[^']*\`/g;
      const pattern2 = /t\(\`[^']*'\)/g;
      
      expect(badCode1.match(pattern1)).toBeTruthy();
      expect(badCode2.match(pattern2)).toBeTruthy();
    });

    it('Should PASS with consistent delimiters', () => {
      const correctCode1 = `const msg = t('hello world');`;
      const correctCode2 = `const msg2 = t(\`hello world\`);`;
      
      const pattern1 = /t\('[^']*\`/g;
      const pattern2 = /t\(\`[^']*'\)/g;
      
      expect(correctCode1.match(pattern1)).toBeNull();
      expect(correctCode2.match(pattern2)).toBeNull();
    });
  });

  describe('Pattern 3: Broken HTML Opening Tags', () => {
    /**
     * ❌ PATTERN: <\s+[a-zA-Z]
     * Tìm: < (khoảng trắng) (chữ cái)
     * VD: "< div" thay vì "<div"
     */
    it('Should NOT have broken HTML opening tags with spaces', () => {
      const pattern = /<\s+[a-zA-Z]/g;
      const matches = appJsContent.match(pattern);
      
      expect(matches, 'Pattern 3: < space tag').toBeNull();
    });

    it('Should trigger FAIL when broken opening tags exist', () => {
      const badCode = `const html = '< div class="box">Content< /div>';`;
      const pattern = /<\s+[a-zA-Z]/g;
      const matches = badCode.match(pattern);
      
      expect(matches).toBeTruthy();
      expect(matches?.includes('< d')).toBe(true);
    });

    it('Should PASS with correct HTML tags', () => {
      const correctCode = `const html = '<div class="box">Content</div>';`;
      const pattern = /<\s+[a-zA-Z]/g;
      const matches = correctCode.match(pattern);
      
      expect(matches).toBeNull();
    });
  });

  describe('Pattern 4: Broken HTML Closing Tags', () => {
    /**
     * ❌ PATTERN: <\/\s+[a-zA-Z]
     * Tìm: </ (khoảng trắng) (chữ cái)
     * VD: "</ div>" thay vì "</div>"
     */
    it('Should NOT have broken HTML closing tags with spaces', () => {
      const pattern = /<\/\s+[a-zA-Z]/g;
      const matches = appJsContent.match(pattern);
      
      expect(matches, 'Pattern 4: </ space tag').toBeNull();
    });

    it('Should trigger FAIL when broken closing tags exist', () => {
      const badCode = `const html = '<div>Content</ div>';`;
      const pattern = /<\/\s+[a-zA-Z]/g;
      const matches = badCode.match(pattern);
      
      expect(matches).toBeTruthy();
      expect(matches?.includes('</ ')).toBe(true);
    });

    it('Should PASS with correct closing tags', () => {
      const correctCode = `const html = '<div>Content</div>';`;
      const pattern = /<\/\s+[a-zA-Z]/g;
      const matches = correctCode.match(pattern);
      
      expect(matches).toBeNull();
    });
  });

  describe('Corruption Detection - Combined Suite', () => {
    /**
     * Run all 4 patterns together để phát hiện bất kỳ lỗi nào
     */
    const allPatterns = [
      { name: 'Pattern 1', regex: /=\s*'[^']*\$\{t\(/ },
      { name: 'Pattern 2a', regex: /t\('[^']*\`/ },
      { name: 'Pattern 2b', regex: /t\(\`[^']*'\)/ },
      { name: 'Pattern 3', regex: /<\s+[a-zA-Z]/ },
      { name: 'Pattern 4', regex: /<\/\s+[a-zA-Z]/ },
    ];

    it('Should pass all corruption pattern checks', () => {
      const failures: string[] = [];
      
      for (const pattern of allPatterns) {
        const matches = appJsContent.match(pattern.regex);
        if (matches) {
          failures.push(`${pattern.name} found: ${matches[0]}`);
        }
      }
      
      expect(
        failures.length,
        `Corruption detected:\n${failures.join('\n')}`
      ).toBe(0);
    });

    it('Should detect multiple corruption types in single code sample', () => {
      const multiCorruptCode = `
        const text1 = '${t("error")}';
        const text2 = t('hello \`world');
        const html = '< button onclick="save()">Save< /button>';
      `;

      const detectedPatterns = allPatterns.filter(p => {
        return multiCorruptCode.match(p.regex);
      });

      expect(detectedPatterns.length).toBeGreaterThanOrEqual(3);
    });
  });

  describe('Syntax Validation with Acorn Parser', () => {
    /**
     * Layer 1 - Step 2: Parse dengan acorn
     * 
     * Acorn parser sẽ catch:
     * - Unclosed brackets
     * - Invalid syntax
     * - Malformed expressions
     */
    it('Should parse app.js successfully with acorn', () => {
      // Wrap content để có valid JavaScript scope
      const wrappedCode = `(function() { ${appJsContent} })();`;
      
      expect(() => {
        parse(wrappedCode, { ecmaVersion: 2020 });
      }).not.toThrow();
    });

    it('Should catch syntax errors with acorn', () => {
      const badCode = `(function() {
        const x = 1;
        const y = 2 // Missing semicolon
        const z = [1, 2, 3; // Wrong bracket
      })();`;

      expect(() => {
        parse(badCode, { ecmaVersion: 2020 });
      }).toThrow();
    });

    it('Should catch unclosed parenthesis', () => {
      const badCode = `(function() {
        function init() {
          console.log('Starting'
        }
      })();`;

      expect(() => {
        parse(badCode, { ecmaVersion: 2020 });
      }).toThrow();
    });

    it('Should catch malformed template literals', () => {
      const badCode = `(function() {
        const msg = \`Hello \${name\`;
      })();`;

      expect(() => {
        parse(badCode, { ecmaVersion: 2020 });
      }).toThrow();
    });

    it('Should accept valid modern JavaScript syntax', () => {
      const goodCode = `(function() {
        const tasks = [{ id: 1, title: 'Test' }];
        const filtered = tasks.filter(t => t.id > 0);
        const message = \`Found \${filtered.length} tasks\`;
        console.log(message);
      })();`;

      expect(() => {
        parse(goodCode, { ecmaVersion: 2020 });
      }).not.toThrow();
    });
  });

  describe('HTML Tag Integrity Check', () => {
    /**
     * Layer 3: Kiểm tra HTML tags match
     * 
     * Tìm tất cả <tag>...</tag> pairs
     * Verify chúng balanced và không broken
     */
    it('Should validate HTML tag pairs', () => {
      const htmlContent = `
        <div id="app">
          <header>
            <h1>TaskFlow</h1>
          </header>
          <main>
            <section id="tasks">
              <ul id="task-list"></ul>
            </section>
          </main>
          <footer>
            <p>© 2026</p>
          </footer>
        </div>
      `;

      // Simple stack-based validation
      const openTags = htmlContent.match(/<[^/\s>][^>]*>/g) || [];
      const closeTags = htmlContent.match(/<\/[^>]+>/g) || [];

      expect(openTags.length).toBe(closeTags.length);
    });

    it('Should detect mismatched tags', () => {
      const brokenHtml = `
        <div id="app">
          <header>
            <h1>Title</div>
          </header>
        </div>
      `;

      const openTags = brokenHtml.match(/<h1>/g) || [];
      const closeTags = brokenHtml.match(/<\/h1>/g) || [];

      // h1 opened but div closed
      expect(openTags.length).not.toBe(closeTags.length);
    });

    it('Should validate deeply nested structures', () => {
      const deepHtml = `
        <div class="container">
          <article>
            <header>
              <h2>Article Title</h2>
            </header>
            <section class="content">
              <p>Paragraph 1</p>
              <p>Paragraph 2</p>
            </section>
          </article>
        </div>
      `;

      const stack: string[] = [];
      const tagRegex = /<\/?([a-zA-Z]\w*)[^>]*>/g;
      let match;

      while ((match = tagRegex.exec(deepHtml)) !== null) {
        const tagName = match[1].toLowerCase();
        if (match[0].startsWith('</')) {
          const lastOpen = stack.pop();
          expect(lastOpen).toBe(tagName);
        } else if (!match[0].endsWith('/>')) {
          stack.push(tagName);
        }
      }

      expect(stack.length).toBe(0);
    });
  });

  describe('Real-world Bug #4 Scenario', () => {
    /**
     * Buổi 5 đã tìm ra Bug #4:
     * const statusText = '${t("task.status.completed")}';
     * 
     * Buổi 6 (hiện tại): Viết test để catch nó trước khi merge
     */
    it('Should catch Bug #4: single-quote template literal', () => {
      const buggyLine = `const statusText = '${t("task.status.completed")}';`;
      const pattern = /=\s*'[^']*\$\{t\(/;

      expect(
        buggyLine.match(pattern),
        'Bug #4 should be detected by Pattern 1 regex'
      ).toBeTruthy();
    });

    it('Should pass after fixing Bug #4 with backticks', () => {
      const fixedLine = `const statusText = \`\${t("task.status.completed")}\`;`;
      const pattern = /=\s*'[^']*\$\{t\(/;

      expect(
        fixedLine.match(pattern),
        'After fix, Pattern 1 should NOT match'
      ).toBeNull();
    });

    it('Should verify template literal executes correctly', () => {
      // Simulate translation function
      const t = (key: string) => {
        const translations: Record<string, string> = {
          'task.status.completed': 'Hoàn thành'
        };
        return translations[key] || key;
      };

      // WRONG way (what Bug #4 does)
      const wrong = '${t("task.status.completed")}'; // Just a string
      expect(wrong).toBe('${t("task.status.completed")}');

      // CORRECT way (fix)
      const correct = `${t("task.status.completed")}`; // Evaluates to 'Hoàn thành'
      expect(correct).toBe('Hoàn thành');
    });
  });

  describe('Test Summary - Safety Gates Pass', () => {
    it('All 7 layers should be checked before deployment', () => {
      const layers = [
        'Layer 1: Syntax Validation (acorn parse)',
        'Layer 2: Function Integrity',
        'Layer 3: Template Safety (HTML tag matching)',
        'Layer 4: Asset References',
        'Layer 5: Corruption Patterns (regex checks)',
        'Layer 6: Import/Export',
        'Layer 7: CSS Validation'
      ];

      expect(layers.length).toBe(7);
      expect(layers[0]).toContain('Syntax Validation');
      expect(layers[4]).toContain('Corruption Patterns');
    });

    it('Should document why Layer 1 is CRITICAL', () => {
      const reason = `
        Case Study: 572 backend tests passed, but 1 syntax error in app.js
        Result: White screen in production, users cannot access the app
        
        Solution: Validate frontend BEFORE deployment:
        1. Corruption pattern regex check (catches 90% of common issues)
        2. Acorn parser validation (catches all remaining syntax errors)
        3. HTML integrity check (prevents broken DOM)
      `;

      expect(reason).toContain('White screen');
      expect(reason).toContain('frontend');
    });
  });
});

3. Chạy Test & Observe Bug #4

Run test:

bash
npm run test -- test/frontend-safety.test.ts

Expected output:

FAIL  test/frontend-safety.test.ts > Frontend Safety - Layer 1 > 
      Pattern 1: Single-quote Template Literal Wrapper > 
      Should NOT have single quotes wrapping template with ${t(...)}

Error: Detected Pattern 1: single-quote template literal
Expected: null
Received: ["=\s*'[^']*\$\{t\("]

4. Fix Bug #4

Vào public/static/app.js, tìm dòng:

javascript
const statusText = '${t("task.status.completed")}';

Sửa thành:

javascript
const statusText = `${t("task.status.completed")}`;

Rerun test → PASS ✅


📝 Kết Luận

Buổi 6 - Frontend Safety Testing:

  • ✅ Hiểu 7 Layers Frontend Safety
  • ✅ Implement 4 Corruption Pattern regex checks
  • ✅ Sử dụng acorn parser để validate syntax
  • ✅ Kiểm tra HTML tag integrity
  • ✅ Catch & fix Bug #4 với automated test

Key Takeaway:
Frontend bugs là CRITICAL vì chúng ảnh hưởng trực tiếp đến UX. Automation test catches 90%+ lỗi trước khi deployment.

Powered by CodyMaster × VitePress