Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
name: Test

on:
pull_request:
branches: [main]
paths:
- 'lib/**'
- 'index.ts'
- 'styles.css'
- 'package.json'
- '__tests__/**'
- 'tsconfig.json'
- '.github/workflows/test.yml'
push:
branches: [main]
paths:
- 'lib/**'
- 'index.ts'
- 'styles.css'
- 'package.json'
- '__tests__/**'
- 'tsconfig.json'
- '.github/workflows/test.yml'

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18, 20, 22]

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
cache: 'yarn'

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Build package
run: yarn build

- name: Run all tests
run: yarn test:all

- name: Check for TypeScript errors
run: yarn build:ts

lint:
runs-on: ubuntu-latest

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
cache: 'yarn'

- name: Install dependencies
run: yarn install --frozen-lockfile

- name: Build package
run: yarn build

- name: Check TypeScript types
run: yarn build:ts --noEmit
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
dist
node_modules
node_modules
.DS_Store
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A powerful TypeScript remark plugin that transforms markdown blockquotes into beautifully styled note elements. Add professional-looking notes, tips, quotes, and more to your markdown documentation with minimal effort!

![GitHub Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/rishichawda/remark-notes-plugin/release.yml)
![Test Status](https://img.shields.io/github/actions/workflow/status/rishichawda/remark-notes-plugin/test.yml?branch=main&label=tests)
![npm](https://img.shields.io/npm/v/remark-notes-plugin)
![License](https://img.shields.io/npm/l/remark-notes-plugin)
![Website](https://img.shields.io/website?url=https%3A%2F%2Frishichawda.github.io%2Fremark-notes-plugin)
Expand Down
80 changes: 80 additions & 0 deletions __tests__/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Tests

## Test Structure

### Test Files

- **`index.ts`**: Fixture-based tests for plugin transformation
- Tests each note type (note, tip, important, quote, bonus)
- Compares actual output against known-good expected output
- Uses `rehype-parse` to parse HTML into HAST for structural comparison
- Strips position data for stable comparisons

- **`styles.ts`**: Tests for automatic style injection
- Verifies styles are automatically injected
- Ensures styles are injected only once per document
- Validates CSS content is not empty

- **`generate-fixtures.ts`**: Utility script to regenerate expected outputs
- Run when you make intentional changes to plugin output. MUST be done with caution.
- Updates all fixture outputs to match current implementation

### Fixtures Directory (`__fixtures__/`)

Each note type has its own directory containing:

- `input.md`: Sample markdown input for that note type
- `output.html`: Expected HTML output (auto-generated)

Structure:

```
__fixtures__/
├── note/
│ ├── input.md
│ └── output.html
├── tip/
│ ├── input.md
│ └── output.html
├── important/
│ ├── input.md
│ └── output.html
├── quote/
│ ├── input.md
│ └── output.html
└── bonus/
├── input.md
└── output.html
```

## Running Tests

```bash
# Run all tests
yarn test

# Run fixture tests only
yarn test:fixtures

# Run style injection tests only
yarn test:styles

# Regenerate expected outputs after changes
yarn generate:fixtures
```

## Testing Approach

1. **Fixture-based testing**: Instead of inline test data, we use separate files for inputs and expected outputs
2. **Structural comparison**: Uses `rehype-parse` to parse HTML into HAST (Hypertext Abstract Syntax Tree) for comparison
3. **Position-agnostic**: Strips position data from HAST to avoid tests that break on whitespace changes
4. **Separation of concerns**: Plugin transformation tests are separate from style injection tests

## Adding New Test Cases

To add a new test case:

1. Create a new directory in `__fixtures__/` (e.g., `__fixtures__/new-type/`)
2. Add `input.md` with your test markdown
3. Run `yarn generate:fixtures` to auto-generate the expected `output.html`
4. Add the new type to the `FIXTURE_TYPES` array in `index.ts`
3 changes: 3 additions & 0 deletions __tests__/__fixtures__/bonus/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
> [!bonus]
> This is a bonus block.
> Extra info for advanced users.
2 changes: 2 additions & 0 deletions __tests__/__fixtures__/bonus/output.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<blockquote class="remark-note remark-note-bonus"><div class="remark-note-header"><span class="remark-note-icon"><svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g stroke-linecap="round" stroke-linejoin="round"></g><g><path d="M9.23163 8.61762C7.26389 9.06284 6.28001 9.28545 6.04594 10.0382C5.81186 10.7909 6.4826 11.5753 7.82408 13.1439L8.17113 13.5498C8.55234 13.9955 8.74294 14.2184 8.82869 14.4942C8.91444 14.7699 8.88562 15.0673 8.82799 15.662L8.77552 16.2035C8.5727 18.2965 8.4713 19.343 9.08412 19.8082C9.69694 20.2734 10.6181 19.8492 12.4605 19.0009L12.9372 18.7815C13.4607 18.5404 13.7225 18.4199 14 18.4199C14.2775 18.4199 14.5393 18.5404 15.0628 18.7815L15.5395 19.0009C17.3819 19.8492 18.3031 20.2734 18.9159 19.8082C19.5287 19.343 19.4273 18.2965 19.2245 16.2035M20.1759 13.1439C21.5174 11.5753 22.1881 10.7909 21.9541 10.0382C21.72 9.28545 20.7361 9.06284 18.7684 8.61762L18.2593 8.50244C17.7001 8.37592 17.4205 8.31266 17.196 8.14225C16.9716 7.97183 16.8276 7.71355 16.5396 7.19699L16.2775 6.7267C15.2641 4.9089 14.7575 4 14 4C13.2425 4 12.7359 4.9089 11.7225 6.7267" stroke-width="1.5" stroke-linecap="round"></path><path d="M2.08887 16C3.20445 15.121 4.68639 14.7971 6.08887 15.1257" stroke-width="1.5" stroke-linecap="round"></path><path d="M2.08887 10.5C3.08887 10 3.37862 10.0605 4.08887 10" stroke-width="1.5" stroke-linecap="round"></path><path d="M2 5.60867L2.20816 5.48676C4.41383 4.19506 6.75032 3.84687 8.95304 4.48161L9.16092 4.54152" stroke-linecap="round" stroke-width="1.5"></path></g></svg></span><span class="remark-note-title">bonus</span></div><div class="remark-note-content"><p>This is a bonus block.
Extra info for advanced users.</p></div></blockquote>
3 changes: 3 additions & 0 deletions __tests__/__fixtures__/important/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
> [!important]
> This is an important block.
> Pay attention to this!
2 changes: 2 additions & 0 deletions __tests__/__fixtures__/important/output.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<blockquote class="remark-note remark-note-important"><div class="remark-note-header"><span class="remark-note-icon"><svg viewBox="0 0 18 18" xmlns="http://www.w3.org/2000/svg" fill="currentColor"><g stroke-width="0"></g><g stroke-linecap="round" stroke-linejoin="round"></g><g><path fill="currentColor" d="M9,14a1.5,1.5,0,1,1,1.5068-1.5A1.5035,1.5035,0,0,1,9,14Z"></path><path fill="currentColor" d="M9,2A7,7,0,1,1,2,9,7.0079,7.0079,0,0,1,9,2M9,0a9,9,0,1,0,9,9A9,9,0,0,0,9,0Z"></path><path fill="currentColor" d="M10,4H8a1,1,0,0,0-.97,1.2425l1,4a1,1,0,0,0,1.94,0l1-4A1,1,0,0,0,10,4Zm0,2h0Z"></path></g></svg></span><span class="remark-note-title">important</span></div><div class="remark-note-content"><p>This is an important block.
Pay attention to this!</p></div></blockquote>
3 changes: 3 additions & 0 deletions __tests__/__fixtures__/note/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
> [!note]
> This is a note block.
> It supports **bold** and *italic* text.
2 changes: 2 additions & 0 deletions __tests__/__fixtures__/note/output.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<blockquote class="remark-note remark-note-note"><div class="remark-note-header"><span class="remark-note-icon"><svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g stroke-linecap="round" stroke-linejoin="round" stroke-width="0.048"></g><g><path d="M8 2V5" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"></path><path d="M16 2V5" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"></path><path d="M21 8.5V17C21 20 19.5 22 16 22H8C4.5 22 3 20 3 17V8.5C3 5.5 4.5 3.5 8 3.5H16C19.5 3.5 21 5.5 21 8.5Z" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"></path><path d="M8 11H16" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"></path><path d="M8 16H12" stroke-width="1.5" stroke-miterlimit="10" stroke-linecap="round" stroke-linejoin="round"></path></g></svg></span><span class="remark-note-title">note</span></div><div class="remark-note-content"><p>This is a note block.
It supports <strong>bold</strong> and <em>italic</em> text.</p></div></blockquote>
3 changes: 3 additions & 0 deletions __tests__/__fixtures__/quote/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
> [!quote]
> This is a quote block.
> "To be or not to be."
2 changes: 2 additions & 0 deletions __tests__/__fixtures__/quote/output.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<blockquote class="remark-note remark-note-quote"><div class="remark-note-header"><span class="remark-note-icon"><svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg"><g stroke-linecap="round" stroke-linejoin="round"></g><g><path d="M14 15V14C14 13.0681 14 12.6022 14.1522 12.2346C14.3552 11.7446 14.7446 11.3552 15.2346 11.1522C15.6022 11 16.0681 11 17 11H17.5C18.9045 11 19.6067 11 20.1111 11.3371C20.3295 11.483 20.517 11.6705 20.6629 11.8889C21 12.3933 21 13.0955 21 14.5V15.3431C21 16.1606 21 16.5694 20.8478 16.9369C20.6955 17.3045 20.4065 17.5935 19.8284 18.1716L19.2396 18.7604C18.7822 19.2178 18 18.8938 18 18.2469V17.8787C18 17.3934 17.6066 17 17.1213 17H16C14.8954 17 14 16.1046 14 15Z" stroke-width="1.5" stroke-linejoin="round"></path><path d="M3 9V8C3 7.06812 3 6.60218 3.15224 6.23463C3.35523 5.74458 3.74458 5.35523 4.23463 5.15224C4.60218 5 5.06812 5 6 5H6.5C7.90446 5 8.60669 5 9.11114 5.33706C9.32952 5.48298 9.51702 5.67048 9.66294 5.88886C10 6.39331 10 7.09554 10 8.5V9.34315C10 10.1606 10 10.5694 9.84776 10.9369C9.69552 11.3045 9.40649 11.5935 8.82843 12.1716L8.23965 12.7604C7.78219 13.2178 7 12.8938 7 12.2469V11.8787C7 11.3934 6.6066 11 6.12132 11H5C3.89543 11 3 10.1046 3 9Z" stroke-width="1.5" stroke-linejoin="round"></path></g></svg></span><span class="remark-note-title">quote</span></div><div class="remark-note-content"><p>This is a quote block.
"To be or not to be."</p></div></blockquote>
3 changes: 3 additions & 0 deletions __tests__/__fixtures__/tip/input.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
> [!tip]
> This is a tip block.
> Use it for helpful advice.
2 changes: 2 additions & 0 deletions __tests__/__fixtures__/tip/output.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<blockquote class="remark-note remark-note-tip"><div class="remark-note-header"><span class="remark-note-icon"><svg viewBox="-0.5 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg"><g stroke-linecap="round" stroke-linejoin="round"></g><g><path d="M19.0006 9.03002C19.0007 8.10058 18.8158 7.18037 18.4565 6.32317C18.0972 5.46598 17.5709 4.68895 16.9081 4.03734C16.2453 3.38574 15.4594 2.87265 14.5962 2.52801C13.7331 2.18336 12.8099 2.01409 11.8806 2.03002C10.0966 2.08307 8.39798 2.80604 7.12302 4.05504C5.84807 5.30405 5.0903 6.98746 5.00059 8.77001C4.95795 9.9595 5.21931 11.1402 5.75999 12.2006C6.30067 13.2609 7.10281 14.1659 8.09058 14.83C8.36897 15.011 8.59791 15.2584 8.75678 15.5499C8.91565 15.8415 8.99945 16.168 9.00059 16.5V18.03H15.0006V16.5C15.0006 16.1689 15.0829 15.843 15.24 15.5515C15.3971 15.26 15.6241 15.0121 15.9006 14.83C16.8528 14.1911 17.6336 13.328 18.1741 12.3167C18.7147 11.3054 18.9985 10.1767 19.0006 9.03002Z" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M15 21.04C14.1345 21.6891 13.0819 22.04 12 22.04C10.9181 22.04 9.86548 21.6891 9 21.04" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path><path d="M11.9901 5.64001L10.3301 8.41998C10.2549 8.54184 10.2138 8.68167 10.2111 8.82483C10.2084 8.96799 10.2441 9.10925 10.3146 9.23389C10.3851 9.35852 10.4877 9.46195 10.6118 9.53339C10.7359 9.60482 10.8769 9.64165 11.0201 9.64001H13.0201C13.1617 9.63947 13.301 9.67657 13.4237 9.7475C13.5463 9.81843 13.6479 9.92063 13.7181 10.0437C13.7883 10.1668 13.8245 10.3063 13.8231 10.4479C13.8217 10.5896 13.7827 10.7283 13.7101 10.85L12.0301 13.64" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"></path></g></svg></span><span class="remark-note-title">tip</span></div><div class="remark-note-content"><p>This is a tip block.
Use it for helpful advice.</p></div></blockquote>
134 changes: 134 additions & 0 deletions __tests__/custom-prefix.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/**
* Test custom class prefix option
*/

import { test } from 'node:test'
import assert from 'node:assert/strict'
import { unified } from 'unified'
import remarkParse from 'remark-parse'
import remarkRehype from 'remark-rehype'
import rehypeRaw from 'rehype-raw'
import rehypeStringify from 'rehype-stringify'
import remarkNotes from '../index.js'

test('remark-notes: custom classPrefix should apply to all elements', async () => {
const markdown = '> [!tip]\n> This is a tip'

const file = await unified()
.use(remarkParse)
.use(remarkNotes, { classPrefix: 'my' })
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeRaw)
.use(rehypeStringify)
.process(markdown)

const output = String(file)

// Container should have both base and type modifier classes with prefix prepended
assert.ok(output.includes('class="my-remark-note my-remark-note-tip"'),
'Container should have prefix prepended to standard remark-note classes')

// Header, icon, title, and content should all use the prefix prepended to remark-note
assert.ok(output.includes('class="my-remark-note-header"'),
'Header should use prefix-remark-note-header pattern')
assert.ok(output.includes('class="my-remark-note-icon"'),
'Icon should use prefix-remark-note-icon pattern')
assert.ok(output.includes('class="my-remark-note-title"'),
'Title should use prefix-remark-note-title pattern')
assert.ok(output.includes('class="my-remark-note-content"'),
'Content should use prefix-remark-note-content pattern')

// Should still contain 'remark-note' as part of the class names
assert.ok(output.includes('remark-note'),
'Should contain remark-note as part of class names')
})

test('remark-notes: all note types work with custom prefix', async () => {
const noteTypes = ['note', 'tip', 'important', 'quote', 'bonus']
const customPrefix = 'custom'

for (const type of noteTypes) {
const markdown = `> [!${type}]\n> Test ${type}`

const file = await unified()
.use(remarkParse)
.use(remarkNotes, { classPrefix: customPrefix })
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeRaw)
.use(rehypeStringify)
.process(markdown)

const output = String(file)

// Check for prefix prepended to remark-note in container classes
assert.ok(
output.includes(`class="${customPrefix}-remark-note ${customPrefix}-remark-note-${type}"`),
`${type} should have prefix prepended: ${customPrefix}-remark-note ${customPrefix}-remark-note-${type}`
)

// Check for prefix prepended to remark-note in all sub-elements
assert.ok(output.includes(`class="${customPrefix}-remark-note-header"`),
`${type} header should use ${customPrefix}-remark-note-header`)
assert.ok(output.includes(`class="${customPrefix}-remark-note-icon"`),
`${type} icon should use ${customPrefix}-remark-note-icon`)
assert.ok(output.includes(`class="${customPrefix}-remark-note-title"`),
`${type} title should use ${customPrefix}-remark-note-title`)
assert.ok(output.includes(`class="${customPrefix}-remark-note-content"`),
`${type} content should use ${customPrefix}-remark-note-content`)
}
})

test('remark-notes: injectStyles option controls style injection', async () => {
const markdown = '> [!note]\n> Test note'

// Test with injectStyles: true (default)
const fileWithStyles = await unified()
.use(remarkParse)
.use(remarkNotes, { injectStyles: true })
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStringify, { allowDangerousHtml: true })
.process(markdown)

const outputWithStyles = String(fileWithStyles)
assert.ok(outputWithStyles.includes('<style>'),
'Styles should be injected when injectStyles is true')

// Test with injectStyles: false
const fileWithoutStyles = await unified()
.use(remarkParse)
.use(remarkNotes, { injectStyles: false })
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeStringify, { allowDangerousHtml: true })
.process(markdown)

const outputWithoutStyles = String(fileWithoutStyles)
assert.ok(!outputWithoutStyles.includes('<style>'),
'Styles should NOT be injected when injectStyles is false')
})

test('remark-notes: combining custom prefix with injectStyles false', async () => {
const markdown = '> [!important]\n> Important message'

const file = await unified()
.use(remarkParse)
.use(remarkNotes, {
classPrefix: 'alert',
injectStyles: false
})
.use(remarkRehype, { allowDangerousHtml: true })
.use(rehypeRaw)
.use(rehypeStringify)
.process(markdown)

const output = String(file)

// Should have prefix prepended to remark-note classes
assert.ok(output.includes('class="alert-remark-note alert-remark-note-important"'),
'Should use prefix prepended to remark-note classes')

// Should NOT have styles
assert.ok(!output.includes('<style>'),
'Should not inject styles')
})

console.log('All custom prefix tests completed!')
Loading