Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the first integration test #124

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 16 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
5 changes: 5 additions & 0 deletions .changeset/flat-months-bow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'steiger': patch
---

The diagnostics from a single rule are now sorted by path in alphabetical order
4 changes: 2 additions & 2 deletions .github/workflows/bundle-size-trusted.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ jobs:
}
}
const fs = require('fs');
const sizes = parseDuOutput(fs.readFileSync(`sizes-${${{ toJson(github.base_ref) }}}.txt`, 'utf8'));
const sizesPR = parseDuOutput(fs.readFileSync(`sizes-${${{ toJson(github.head_ref) }}}.txt`, 'utf8'));
const sizes = parseDuOutput(fs.readFileSync('sizes-master.txt', 'utf8'));
const sizesPR = parseDuOutput(fs.readFileSync(`sizes-${${{ toJson(github.event.workflow_run.head_branch) }}}.txt`, 'utf8'));
core.summary.addHeading('📊 Package size report', '3');
core.summary.addTable([
['Package', 'Before', 'After'].map((data) => ({ data, header: true })),
Expand Down
1 change: 1 addition & 0 deletions examples/kitchen-sink-of-fsd-issues/src/app/ui/App.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export const App = {}
1 change: 1 addition & 0 deletions examples/kitchen-sink-of-fsd-issues/src/app/ui/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { App } from './App'
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { App } from '@/app/ui/App'

console.log(App)
12 changes: 12 additions & 0 deletions examples/kitchen-sink-of-fsd-issues/tsconfig.app.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"compilerOptions": {
"module": "ESNext",
"jsx": "react-jsx",
"moduleResolution": "Bundler",
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": ["src"]
}
5 changes: 4 additions & 1 deletion examples/kitchen-sink-of-fsd-issues/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
{}
{
"files": [],
"references": [{ "path": "./tsconfig.app.json" }]
}
1 change: 1 addition & 0 deletions integration-tests/eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from '@steiger/eslint-config'
37 changes: 37 additions & 0 deletions integration-tests/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"private": true,
"name": "@steiger/integration-tests",
"description": "Integration tests for Steiger",
"version": "0.1.0",
"scripts": {
"test": "vitest run",
"update-windows-snapshots": "node ./scripts/update-windows-snapshots.mjs"
},
"exports": {
".": "./src/index.ts"
},
"type": "module",
"authors": [
{
"name": "Lev Chelyadinov",
"email": "[email protected]",
"url": "https://github.com/illright"
}
],
"devDependencies": {
"@steiger/eslint-config": "workspace:*",
"@steiger/tsconfig": "workspace:*",
"@total-typescript/ts-reset": "^0.6.1",
"@types/node": "^18.11.9",
"eslint": "^9.16.0",
"figures": "^6.1.0",
"get-bin-path": "^11.0.0",
"prettier": "^3.4.2",
"tinyexec": "^0.3.1",
"typescript": "^5.7.2",
"vitest": "^2.1.8"
},
"dependencies": {
"steiger": "workspace:*"
}
}
2 changes: 2 additions & 0 deletions integration-tests/reset.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Do not add any other lines of code to this file!
import '@total-typescript/ts-reset'
23 changes: 23 additions & 0 deletions integration-tests/scripts/update-windows-snapshots.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/**
* Run this script on Mac or Linux when you update test snapshots to port these changes over to Windows snapshots.
*
* $ pnpm run update-windows-snapshots
*/

import * as fs from 'node:fs/promises'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { replaceSymbols } from 'figures'

const snapshotsFolder = join(dirname(fileURLToPath(import.meta.url)), '../tests/__snapshots__')

// Go through each POSIX snapshot and replace the forward slashes in diagnostic paths with backslashes
for (const file of await fs.readdir(snapshotsFolder)) {
if (file.endsWith('-posix.txt')) {
const windowsSnapshotName = file.slice(0, -'-posix.txt'.length) + '-windows.txt'
const updatedSnapshot = replaceSymbols(await fs.readFile(join(snapshotsFolder, file), 'utf8'), {
useFallback: true,
}).replace(/(?<=┌.+)\//gm, '\\')
await fs.writeFile(join(snapshotsFolder, windowsSnapshotName), updatedSnapshot)
}
}
45 changes: 45 additions & 0 deletions integration-tests/tests/__snapshots__/smoke-stderr-posix.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

┌ src/entities/user/api/getUser.ts
✘ Forbidden import from higher layer "app".
└ fsd/forbidden-imports (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/forbidden-imports​)

┌ src/entities
✘ Inconsistent pluralization of slice names. Prefer all plural names
✔ Auto-fixable
└ fsd/inconsistent-naming (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/inconsistent-naming​)

┌ src/entities/user
✘ This slice has no references. Consider removing it.
└ fsd/insignificant-slice (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice​)

┌ src/entities/users
✘ This slice has no references. Consider removing it.
└ fsd/insignificant-slice (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice​)

┌ src/entities/user/api/getUser.ts
✘ Forbidden sidestep of public API when importing from "@/app/ui/App".
└ fsd/no-public-api-sidestep (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-public-api-sidestep​)

┌ src/entities/user/ui/api
✘ Having a folder with the name "api" inside a segment could be confusing because that name is commonly used for segments. Consider renaming it.
└ fsd/no-reserved-folder-names (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-reserved-folder-names​)

┌ src/app/ui
✘ Layer "app" should not have "ui" segment.
└ fsd/no-ui-in-app (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-ui-in-app​)

┌ src/processes
✘ Layer "processes" is deprecated, avoid using it
└ fsd/no-processes (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-processes​)

────────────────────────────────────────────────────────
Found 8 errors (1 can be fixed automatically with --fix)

45 changes: 45 additions & 0 deletions integration-tests/tests/__snapshots__/smoke-stderr-windows.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@

┌ src\entities\user\api\getUser.ts
× Forbidden import from higher layer "app".
└ fsd/forbidden-imports (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/forbidden-imports​)

┌ src\entities
× Inconsistent pluralization of slice names. Prefer all plural names
√ Auto-fixable
└ fsd/inconsistent-naming (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/inconsistent-naming​)

┌ src\entities\user
× This slice has no references. Consider removing it.
└ fsd/insignificant-slice (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice​)

┌ src\entities\users
× This slice has no references. Consider removing it.
└ fsd/insignificant-slice (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/insignificant-slice​)

┌ src\entities\user\api\getUser.ts
× Forbidden sidestep of public API when importing from "@/app/ui/App".
└ fsd/no-public-api-sidestep (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-public-api-sidestep​)

┌ src\entities\user\ui\api
× Having a folder with the name "api" inside a segment could be confusing because that name is commonly used for segments. Consider renaming it.
└ fsd/no-reserved-folder-names (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-reserved-folder-names​)

┌ src\app\ui
× Layer "app" should not have "ui" segment.
└ fsd/no-ui-in-app (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-ui-in-app​)

┌ src\processes
× Layer "processes" is deprecated, avoid using it
└ fsd/no-processes (​https://github.com/feature-sliced/steiger/tree/master/packages/steiger-plugin-fsd/src/no-processes​)

────────────────────────────────────────────────────────
Found 8 errors (1 can be fixed automatically with --fix)

24 changes: 24 additions & 0 deletions integration-tests/tests/smoke.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as fs from 'node:fs/promises'
import os from 'node:os'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { exec } from 'tinyexec'

import { expect, test } from 'vitest'

import { getSteigerBinPath } from '../utils/get-bin-path.js'

const temporaryDirectory = await fs.realpath(os.tmpdir())
const steiger = await getSteigerBinPath()
const kitchenSinkExample = join(dirname(fileURLToPath(import.meta.url)), '../../examples/kitchen-sink-of-fsd-issues')
const pathPlatform = os.platform() === 'win32' ? 'windows' : 'posix'

test('basic functionality in the kitchen sink example project', async () => {
const project = join(temporaryDirectory, 'smoke')
await fs.rm(project, { recursive: true, force: true })
await fs.cp(kitchenSinkExample, project, { recursive: true })

const { stderr } = await exec('node', [steiger, 'src'], { nodeOptions: { cwd: project, env: { NO_COLOR: '1' } } })

await expect(stderr).toMatchFileSnapshot(join('__snapshots__', `smoke-stderr-${pathPlatform}.txt`))
})
7 changes: 7 additions & 0 deletions integration-tests/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@steiger/tsconfig/base.json",
"include": ["./tests", "./reset.d.ts", "utils/get-bin-path.ts"],
"compilerOptions": {
"tsBuildInfoFile": "node_modules/.cache/tsbuildinfo.json"
}
}
22 changes: 22 additions & 0 deletions integration-tests/utils/get-bin-path.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { promises as fs } from 'node:fs'
import * as process from 'node:process'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
import { getBinPath } from 'get-bin-path'

/**
* Resolve the full path to the built JS file of Steiger.
*
* Rejects if the file doesn't exist.
*/
export async function getSteigerBinPath() {
const steiger = (await getBinPath({ cwd: join(dirname(fileURLToPath(import.meta.url)), '../../packages/steiger') }))!
try {
await fs.stat(steiger)
} catch {
console.error('Run `turbo build` before running integration tests')
process.exit(1)
}

return steiger
}
21 changes: 9 additions & 12 deletions packages/steiger/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,25 +21,22 @@ async function runRules({ vfs, rules }: { vfs: Folder; rules: Array<Rule> }) {
const vfsWithoutGlobalIgnores = removeGlobalIgnoreFromVfs(vfs, getGlobalIgnores())

const ruleResults = await Promise.all(rules.map((rule) => runRule(vfsWithoutGlobalIgnores, rule)))
return ruleResults.flatMap((r, ruleResultsIndex) => {
const { diagnostics } = r
if (diagnostics.length === 0) {
return []
}
daniilsapa marked this conversation as resolved.
Show resolved Hide resolved

return ruleResults.flatMap(({ diagnostics }, ruleResultsIndex) => {
const ruleName = rules[ruleResultsIndex].name
const severities = calculateFinalSeverities(
vfsWithoutGlobalIgnores,
ruleName,
diagnostics.map((d) => d.location.path),
)

return diagnostics.map((d, index) => ({
...d,
ruleName,
getRuleDescriptionUrl,
severity: severities[index],
}))
return diagnostics
.sort((a, b) => a.location.path.localeCompare(b.location.path))
.map((d, index) => ({
...d,
ruleName,
getRuleDescriptionUrl,
severity: severities[index],
}))
})
}

Expand Down
Loading
Loading