next.js/test/unit/eslint-plugin-next/no-html-link-for-pages.test.ts
no-html-link-for-pages.test.ts499 lines13.3 KB
/* eslint-env jest */
import { Linter } from 'eslint'
import { rules } from '@next/eslint-plugin-next'
import assert from 'assert'
import path from 'path'

const NextESLintRule = rules['no-html-link-for-pages']

const withCustomPagesDir = path.join(__dirname, 'with-custom-pages-dir')
const withNestedPagesDir = path.join(__dirname, 'with-nested-pages-dir')
const withoutPagesDir = path.join(__dirname, 'without-pages-dir')
const withAppDir = path.join(__dirname, 'with-app-dir')

const linters = {
  withoutPages: new Linter({
    cwd: withoutPagesDir,
    configType: 'eslintrc',
  }),
  withApp: new Linter({
    cwd: withAppDir,
    configType: 'eslintrc',
  }),
  withNestedPages: new Linter({
    cwd: withNestedPagesDir,
    configType: 'eslintrc',
  }),
  withCustomPages: new Linter({
    cwd: withCustomPagesDir,
    configType: 'eslintrc',
  }),
}

const linterConfig: any = {
  rules: {
    'no-html-link-for-pages': [2],
  },
  parserOptions: {
    ecmaVersion: 2018,
    sourceType: 'module',
    ecmaFeatures: {
      modules: true,
      jsx: true,
    },
  },
}
const linterConfigWithCustomDirectory: any = {
  ...linterConfig,
  rules: {
    'no-html-link-for-pages': [
      2,
      path.join(withCustomPagesDir, 'custom-pages'),
    ],
  },
}
const linterConfigWithMultipleDirectories = {
  ...linterConfig,
  rules: {
    'no-html-link-for-pages': [
      2,
      [
        path.join(withCustomPagesDir, 'custom-pages'),
        path.join(withCustomPagesDir, 'custom-pages/list'),
      ],
    ],
  },
}
const linterConfigWithNestedContentRootDirDirectory = {
  ...linterConfig,
  settings: {
    next: {
      rootDir: path.join(withNestedPagesDir, 'demos/with-nextjs'),
    },
  },
}

for (const linter of Object.values(linters)) {
  linter.defineRules({
    'no-html-link-for-pages': NextESLintRule,
  })
}

const validCode = `
import Link from 'next/link';

export class Blah extends Head {
  render() {
    return (
      <div>
        <Link href='/'>
          <a>Homepage</a>
        </Link>
        <h1>Hello title</h1>
      </div>
    );
  }
}
`

const validAnchorCode = `
import Link from 'next/link';

export class Blah extends Head {
  render() {
    return (
      <div>
        <a href='#heading'>Homepage</a>
        <h1>Hello title</h1>
      </div>
    );
  }
}
`

const validExternalLinkCode = `
import Link from 'next/link';

export class Blah extends Head {
  render() {
    return (
      <div>
        <a href='https://google.com/'>Homepage</a>
        <h1>Hello title</h1>
      </div>
    );
  }
}
`

const validDownloadLinkCode = `
import Link from 'next/link';

export class Blah extends Head {
  render() {
    return (
      <div>
        <a href='/static-file.csv' download>Download</a>
        <h1>Hello title</h1>
      </div>
    );
  }
}
`
const validTargetBlankLinkCode = `
import Link from 'next/link';

export class Blah extends Head {
  render() {
    return (
      <div>
        <a target="_blank" href='/new-tab'>New Tab</a>
        <h1>Hello title</h1>
      </div>
    );
  }
}
`

const validPublicFile = `
import Link from 'next/link';

export class Blah extends Head {
  render() {
    return (
      <div>
        <a href='/presentation.pdf'>View PDF</a>
        <h1>Hello title</h1>
      </div>
    );
  }
}
`

const invalidStaticCode = `
import Link from 'next/link';

export class Blah extends Head {
  render() {
    return (
      <div>
        <a href='/'>Homepage</a>
        <h1>Hello title</h1>
      </div>
    );
  }
}
`

const invalidDynamicCode = `
import Link from 'next/link';

export class Blah extends Head {
  render() {
    return (
      <div>
        <a href='/list/foo/bar'>Homepage</a>
        <h1>Hello title</h1>
      </div>
    );
  }
}
`
const secondInvalidDynamicCode = `
import Link from 'next/link';

export class Blah extends Head {
  render() {
    return (
      <div>
        <a href='/list/foo/'>Homepage</a>
        <h1>Hello title</h1>
      </div>
    );
  }
}
`

const thirdInvalidDynamicCode = `
import Link from 'next/link';

export class Blah extends Head {
  render() {
    return (
      <div>
        <a href='/list/lorem-ipsum/'>Homepage</a>
        <h1>Hello title</h1>
      </div>
    );
  }
}
`
const validInterceptedRouteCode = `
import Link from 'next/link';
export class Blah extends Head {
  render() {
    return (
      <div>
        <Link href='/photo/1/'>Photo</Link>
        <h1>Hello title</h1>
      </div>
    );
  }
}
`

const invalidInterceptedRouteCode = `
import Link from 'next/link';
export class Blah extends Head {
  render() {
    return (
      <div>
        <a href='/photo/1/'>Photo</a>
        <h1>Hello title</h1>
      </div>
    );
  }
}
`
describe('no-html-link-for-pages', function () {
  it('does not print warning when there are "pages" or "app" directories with rootDir in context settings', function () {
    const consoleSpy = jest.spyOn(console, 'warn').mockImplementation()
    linters.withNestedPages.verify(
      validCode,
      linterConfigWithNestedContentRootDirDirectory,
      { filename: 'foo.js' }
    )
    expect(consoleSpy).not.toHaveBeenCalled()
    consoleSpy.mockRestore()
  })
  it('prints warning when there are no "pages" or "app" directories', function () {
    const consoleSpy = jest.spyOn(console, 'warn').mockImplementation()
    linters.withoutPages.verify(validCode, linterConfig, {
      filename: 'foo.js',
    })
    expect(consoleSpy).toHaveBeenCalledWith(
      `Pages directory cannot be found at ${path.join(
        withoutPagesDir,
        'pages'
      )} or ${path.join(
        withoutPagesDir,
        'src',
        'pages'
      )}. If using a custom path, please configure with the \`no-html-link-for-pages\` rule in your eslint config file.`
    )
    consoleSpy.mockRestore()
  })
  it('does not print warning when there is "app" directory and no "pages" directory', function () {
    const consoleSpy = jest.spyOn(console, 'warn').mockImplementation()
    linters.withApp.verify(validCode, linterConfig, {
      filename: 'foo.js',
    })
    expect(consoleSpy).not.toHaveBeenCalled()
    consoleSpy.mockRestore()
  })
  it('valid link element', function () {
    const report = linters.withCustomPages.verify(
      validCode,
      linterConfigWithCustomDirectory,
      { filename: 'foo.js' }
    )
    assert.deepEqual(report, [])
  })
  it('valid link element with multiple directories', function () {
    const report = linters.withCustomPages.verify(
      validCode,
      linterConfigWithMultipleDirectories,
      { filename: 'foo.js' }
    )
    assert.deepEqual(report, [])
  })
  it('valid anchor element', function () {
    const report = linters.withCustomPages.verify(
      validAnchorCode,
      linterConfigWithCustomDirectory,
      { filename: 'foo.js' }
    )
    assert.deepEqual(report, [])
  })
  it('valid external link element', function () {
    const report = linters.withCustomPages.verify(
      validExternalLinkCode,
      linterConfigWithCustomDirectory,
      { filename: 'foo.js' }
    )
    assert.deepEqual(report, [])
  })
  it('valid download link element', function () {
    const report = linters.withCustomPages.verify(
      validDownloadLinkCode,
      linterConfigWithCustomDirectory,
      { filename: 'foo.js' }
    )
    assert.deepEqual(report, [])
  })
  it('valid target="_blank" link element', function () {
    const report = linters.withCustomPages.verify(
      validTargetBlankLinkCode,
      linterConfigWithCustomDirectory,
      { filename: 'foo.js' }
    )
    assert.deepEqual(report, [])
  })
  it('valid public file link element', function () {
    const report = linters.withCustomPages.verify(
      validPublicFile,
      linterConfigWithCustomDirectory,
      { filename: 'foo.js' }
    )
    assert.deepEqual(report, [])
  })
  it('invalid static route', function () {
    const [report] = linters.withCustomPages.verify(
      invalidStaticCode,
      linterConfigWithCustomDirectory,
      { filename: 'foo.js' }
    )
    assert.notEqual(report, undefined, 'No lint errors found.')
    assert.equal(
      report.message,
      'Do not use an `<a>` element to navigate to `/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
    )
  })
  it('invalid dynamic route', function () {
    const [report] = linters.withCustomPages.verify(
      invalidDynamicCode,
      linterConfigWithCustomDirectory,
      { filename: 'foo.js' }
    )
    assert.notEqual(report, undefined, 'No lint errors found.')
    assert.equal(
      report.message,
      'Do not use an `<a>` element to navigate to `/list/foo/bar/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
    )
    const [secondReport] = linters.withCustomPages.verify(
      secondInvalidDynamicCode,
      linterConfigWithCustomDirectory,
      { filename: 'foo.js' }
    )
    assert.notEqual(secondReport, undefined, 'No lint errors found.')
    assert.equal(
      secondReport.message,
      'Do not use an `<a>` element to navigate to `/list/foo/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
    )
    const [thirdReport] = linters.withCustomPages.verify(
      thirdInvalidDynamicCode,
      linterConfigWithCustomDirectory,
      { filename: 'foo.js' }
    )
    assert.notEqual(thirdReport, undefined, 'No lint errors found.')
    assert.equal(
      thirdReport.message,
      'Do not use an `<a>` element to navigate to `/list/lorem-ipsum/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
    )
  })
  it('valid link element with appDir', function () {
    const report = linters.withApp.verify(validCode, linterConfig, {
      filename: 'foo.js',
    })
    assert.deepEqual(report, [])
  })
  it('valid link element with multiple directories with appDir', function () {
    const report = linters.withApp.verify(validCode, linterConfig, {
      filename: 'foo.js',
    })
    assert.deepEqual(report, [])
  })
  it('valid anchor element with appDir', function () {
    const report = linters.withApp.verify(validAnchorCode, linterConfig, {
      filename: 'foo.js',
    })
    assert.deepEqual(report, [])
  })
  it('valid external link element with appDir', function () {
    const report = linters.withApp.verify(validExternalLinkCode, linterConfig, {
      filename: 'foo.js',
    })
    assert.deepEqual(report, [])
  })
  it('valid download link element with appDir', function () {
    const report = linters.withApp.verify(validDownloadLinkCode, linterConfig, {
      filename: 'foo.js',
    })
    assert.deepEqual(report, [])
  })
  it('valid target="_blank" link element with appDir', function () {
    const report = linters.withApp.verify(
      validTargetBlankLinkCode,
      linterConfig,
      { filename: 'foo.js' }
    )
    assert.deepEqual(report, [])
  })
  it('valid public file link element with appDir', function () {
    const report = linters.withApp.verify(validPublicFile, linterConfig, {
      filename: 'foo.js',
    })
    assert.deepEqual(report, [])
  })
  it('invalid static route with appDir', function () {
    const [report] = linters.withApp.verify(invalidStaticCode, linterConfig, {
      filename: 'foo.js',
    })
    assert.notEqual(report, undefined, 'No lint errors found.')
    assert.equal(
      report.message,
      'Do not use an `<a>` element to navigate to `/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
    )
  })
  it('invalid dynamic route with appDir', function () {
    const [report] = linters.withApp.verify(invalidDynamicCode, linterConfig, {
      filename: 'foo.js',
    })
    assert.notEqual(report, undefined, 'No lint errors found.')
    assert.equal(
      report.message,
      'Do not use an `<a>` element to navigate to `/list/foo/bar/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
    )
    const [secondReport] = linters.withApp.verify(
      secondInvalidDynamicCode,
      linterConfig,
      { filename: 'foo.js' }
    )
    assert.notEqual(secondReport, undefined, 'No lint errors found.')
    assert.equal(
      secondReport.message,
      'Do not use an `<a>` element to navigate to `/list/foo/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
    )
    const [thirdReport] = linters.withApp.verify(
      thirdInvalidDynamicCode,
      linterConfig,
      { filename: 'foo.js' }
    )
    assert.notEqual(thirdReport, undefined, 'No lint errors found.')
    assert.equal(
      thirdReport.message,
      'Do not use an `<a>` element to navigate to `/list/lorem-ipsum/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
    )
  })
  it('valid intercepted route with appDir', function () {
    const report = linters.withApp.verify(
      validInterceptedRouteCode,
      linterConfig,
      { filename: 'foo.js' }
    )
    assert.deepEqual(report, [])
  })
  it('invalid intercepted route with appDir', function () {
    const [report] = linters.withApp.verify(
      invalidInterceptedRouteCode,
      linterConfig,
      { filename: 'foo.js' }
    )
    assert.notEqual(report, undefined, 'No lint errors found.')
    assert.equal(
      report.message,
      'Do not use an `<a>` element to navigate to `/photo/1/`. Use `<Link />` from `next/link` instead. See: https://nextjs.org/docs/messages/no-html-link-for-pages'
    )
  })
})
Quest for Codev2.0.0
/
SIGN IN