- {{ . | markdownify }} + {{ . | string | markdownify }}
{{ end }} - {{ .content | markdownify }} + {{ .content | string | markdownify }}diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 000000000..5123e5211 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,107 @@ +name: Test + +on: + pull_request: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Hugo + uses: peaceiris/actions-hugo@v2 + with: + hugo-version: 0.163.3 + extended: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Load documentation content + run: make prepare + + - name: Build site + run: make production-build + + - name: Upload build output + uses: actions/upload-artifact@v4 + if: always() + with: + name: build-output + path: public/ + retention-days: 5 + + test: + runs-on: ubuntu-latest + needs: build + + steps: + - uses: actions/checkout@v4 + + - name: Set up Hugo + uses: peaceiris/actions-hugo@v2 + with: + hugo-version: 0.163.3 + extended: true + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '18' + cache: 'npm' + + - name: Install dependencies + run: npm ci + + - name: Install Playwright browsers + run: npx playwright install --with-deps chromium + + - name: Load documentation content + run: make prepare + + - name: Run E2E tests + run: npm run test:e2e + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 7 + + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Check for trailing whitespace + run: | + if grep -r '[[:space:]]$' --include='*.md' --include='*.toml' --include='*.yaml' --include='*.yml' .; then + echo "❌ Found trailing whitespace" + exit 1 + fi + echo "✅ No trailing whitespace" + + - name: Validate YAML files + run: | + for file in .github/workflows/*.yml; do + echo "Checking $file..." + if ! python3 -c "import yaml; yaml.safe_load(open('$file'))" 2>/dev/null; then + echo "❌ Invalid YAML: $file" + exit 1 + fi + done + echo "✅ All YAML files valid" diff --git a/.gitignore b/.gitignore index c23fd50ca..b2edc058e 100644 --- a/.gitignore +++ b/.gitignore @@ -8,8 +8,9 @@ node_modules/ public*/ resources/ -# Content from goharbor/harbor repo +# Content from goharbor/harbor repo (generated at build time) content/docs/ +content/cli-docs/ generated/ # Link checker artifacts @@ -46,3 +47,8 @@ Thumbs.db *.un~ .idea/ .hugo_build.lock + +# Playwright test artifacts # +###################### +test-results/ +playwright-report/ diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..8aaf841e4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,29 @@ +FROM debian:bookworm-slim + +# Install dependencies +RUN apt-get update && apt-get install -y \ + wget \ + ca-certificates \ + nodejs \ + npm && \ + rm -rf /var/lib/apt/lists/* + +# Install Hugo 0.163.3 extended +RUN wget -q https://github.com/gohugoio/hugo/releases/download/v0.163.3/hugo_extended_0.163.3_linux-amd64.tar.gz && \ + tar xzf hugo_extended_0.163.3_linux-amd64.tar.gz -C /usr/local/bin && \ + rm hugo_extended_0.163.3_linux-amd64.tar.gz && \ + hugo version + +WORKDIR /site + +# Copy project files +COPY . /site/ + +# Install npm dependencies +RUN npm install + +# Expose Hugo server port +EXPOSE 1313 + +# Default command: run Hugo server +CMD ["hugo", "server", "--bind", "0.0.0.0", "--buildDrafts", "--buildFuture", "--disableFastRender"] diff --git a/Makefile b/Makefile index 62e4e6dee..b072524e1 100644 --- a/Makefile +++ b/Makefile @@ -38,3 +38,9 @@ check-internal-links: clean build link-checker-setup run-checker check-all-links: clean build link-checker-setup bin/htmltest --conf .htmltest.external.yml + +test: + npm run test:e2e + +test-ui: + npm run test:e2e:ui diff --git a/README.md b/README.md index 92c13fe5b..90d2e539a 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,28 @@ make serve This starts up the local Hugo server on http://localhost:1313. As you make changes, the site refreshes automatically in your browser. +## Testing + +### Running E2E Tests + +The website includes Playwright E2E tests that validate rendering and functionality: + +```sh +make test +``` + +To run tests in interactive UI mode: + +```sh +make test-ui +``` + +Tests are located in the [`e2e/`](./e2e) directory and validate: +- Admonition shortcodes render correctly +- Custom output formats (e.g., `_redirects` for Netlify) +- CSS pipeline and styling +- Site configuration changes + ## Checking links To run the link checker for the Harbor website: diff --git a/config.toml b/config.toml index b00764458..e7f989324 100644 --- a/config.toml +++ b/config.toml @@ -1,6 +1,6 @@ title = "Harbor" baseURL = "https://goharbor.io" -disableKinds = ["taxonomy", "taxonomyTerm"] +disableKinds = ["taxonomy", "term"] ignoreFiles = ["README.md"] [params] @@ -163,7 +163,7 @@ weight = 4 home = [ "HTML", "REDIRECTS" ] [mediaTypes."text/netlify"] -delimiter = "" +suffixes = [] [outputFormats.REDIRECTS] mediaType = "text/netlify" diff --git a/e2e/breaking-changes.spec.ts b/e2e/breaking-changes.spec.ts new file mode 100644 index 000000000..9c889a931 --- /dev/null +++ b/e2e/breaking-changes.spec.ts @@ -0,0 +1,83 @@ +import { test, expect } from '@playwright/test'; +import * as fs from 'fs'; +import * as path from 'path'; + +test.describe('Hugo breaking changes are fixed', () => { + test('admonition shortcodes render HTML correctly (markdownify fix)', async ({ page }) => { + // Use a docs page known to have admonitions + await page.goto('/docs/2.1.0/administration/configuring-replication/'); + + // Check that admonition elements exist + const admonitions = page.locator('.admonition'); + await expect(admonitions).not.toHaveCount(0); + + // Verify content is rendered, not raw markdown + const firstAdmonition = admonitions.first().locator('.content'); + const innerHTML = await firstAdmonition.innerHTML(); + + // If markdownify failed, we'd see raw markdown syntax like `**bold**` or `[link](url)` + expect(innerHTML).not.toMatch(/\*\*/); + expect(innerHTML).not.toMatch(/\[.*\]\(.*\)/); + + // Verify there's actual rendered text + expect(innerHTML.length).toBeGreaterThan(0); + }); + + test('disableKinds "term" produces no taxonomy pages', async () => { + // The disableKinds config prevents taxonomy/term pages from being built. + // Verify the built output has no taxonomy index pages. + const publicDir = path.join(process.cwd(), 'public'); + + const taxonomyDir = path.join(publicDir, 'tags'); + const termDir = path.join(publicDir, 'categories'); + + expect(fs.existsSync(taxonomyDir)).toBe(false); + expect(fs.existsSync(termDir)).toBe(false); + }); + + test('custom output format _redirects is generated', async () => { + const publicDir = path.join(process.cwd(), 'public'); + const redirectsFile = path.join(publicDir, '_redirects'); + + expect(fs.existsSync(redirectsFile)).toBe(true); + + const content = fs.readFileSync(redirectsFile, 'utf-8'); + expect(content.length).toBeGreaterThan(0); + + // Should contain redirect rules (format: /source /destination 301) + expect(content).toMatch(/^\/.+\s+/m); + }); + + test('CSS pipeline processes without errors (css.Sass)', async ({ page }) => { + await page.goto('/'); + + // Verify stylesheet link exists in head (link elements are never "visible" in Playwright) + const styleLink = page.locator('link[rel="stylesheet"]').first(); + await expect(styleLink).toHaveCount(1); + + const href = await styleLink.getAttribute('href'); + expect(href).toBeTruthy(); + expect(href).toContain('.css'); + + // Verify the stylesheet loaded by checking computed styles are applied + const body = page.locator('body'); + const computedStyle = await body.evaluate(el => + window.getComputedStyle(el).backgroundColor + ); + expect(computedStyle).not.toBe(''); + }); + + test('built CSS is fingerprinted and valid', async () => { + // In production build (hugo build), CSS should be fingerprinted + const publicDir = path.join(process.cwd(), 'public'); + const cssDir = path.join(publicDir, 'css'); + + const files = fs.readdirSync(cssDir); + const fingerprinted = files.find(f => f.match(/\.[a-f0-9]{64}\.css$/)); + + expect(fingerprinted).toBeDefined(); + + const content = fs.readFileSync(path.join(cssDir, fingerprinted!), 'utf-8'); + expect(content.length).toBeGreaterThan(100000); + }); +}); diff --git a/layouts/partials/admonition.html b/layouts/partials/admonition.html index dc37b6529..7701006e3 100644 --- a/layouts/partials/admonition.html +++ b/layouts/partials/admonition.html @@ -10,11 +10,11 @@
- {{ . | markdownify }} + {{ . | string | markdownify }}
{{ end }} - {{ .content | markdownify }} + {{ .content | string | markdownify }}