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
146 changes: 146 additions & 0 deletions .github/workflows/Essential-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
name: Essential tests

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

on:
pull_request:
branches: [master]
paths:
- 'app/renderer/src/main/src/__tests__/**'
- '.github/workflows/**'

jobs:
vitest:
name: Vitest - renderer main
runs-on: ubuntu-latest
steps:
- name: Display incoming configuration parameters
run: echo ${{ inputs.platform }} ${{ inputs.legacy }} ${{ inputs.version }} ${{ inputs.engine }} ${{ inputs.engineVersion }} ${{ inputs.devTool }}

- name: Checkout
uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 18.18.2
cache: "yarn"
cache-dependency-path: |
yarn.lock
app/renderer/src/main/yarn.lock
app/renderer/engine-link-startup/yarn.lock

# - name: Cache Yarn cache
# id: yarn-cache
# uses: actions/cache@v4
# with:
# path: ~/.cache/yarn
# key: ${{ runner.os }}-yarn-cache-${{ hashFiles('app/renderer/src/main/yarn.lock') }}
# restore-keys: |
# ${{ runner.os }}-yarn-cache-

# - name: Cache node_modules
# id: node-mod-cache
# uses: actions/cache@v4
# with:
# path: app/renderer/src/main/node_modules
# key: ${{ runner.os }}-node-modules-${{ hashFiles('app/renderer/src/main/yarn.lock') }}
# restore-keys: |
# ${{ runner.os }}-node-modules-

- name: Install dependencies (renderer main)
# if: steps.node-mod-cache.outputs.cache-hit != 'true'
working-directory: app/renderer/src/main
run: yarn install --frozen-lockfile --prefer-offline --network-concurrency 1

- name: Skip install (node_modules cache hit)
# if: steps.node-mod-cache.outputs.cache-hit == 'true'
run: echo "node_modules cache restored; skipping yarn install"

- name: Verify installed packages (quick)
working-directory: app/renderer/src/main
run: |
echo "Yarn cache dir: $(yarn cache dir)"
ls -la node_modules | head -n 20 || true

- name: Debug - list candidate test files
working-directory: app/renderer/src/main
run: |
echo "pwd: $(pwd)"
echo "node: $(node -v) yarn: $(yarn -v)"
echo "Find test/spec files (up to depth 6):"
find . -maxdepth 6 -type f \( -name "*.test.*" -o -name "*.spec.*" \) -print || true
echo "Show specific folder (if exists):"
ls -la src/components/playground/Vitest__Test__ || true

- name: Determine relevant test files
id: test_selection
working-directory: app/renderer/src/main
shell: bash
run: |
# Get files changed in the PR. Use multiple fallbacks because the runner
# may have a shallow checkout and referenced commits might not exist.
CHANGED=""

# 1) Try the standard range if both commits are present
if git rev-parse --verify "${{ github.event.before }}" >/dev/null 2>&1 && git rev-parse --verify "${{ github.sha }}" >/dev/null 2>&1; then
CHANGED=$(git diff --name-only "${{ github.event.before }}" "${{ github.sha }}" || true)
fi

# 2) If still empty, try fetching the base branch (for PRs) and compare
if [ -z "$CHANGED" ] && [ -n "${{ github.event.pull_request.base.ref }}" ]; then
echo "Standard range not available — fetching base branch ${{ github.event.pull_request.base.ref }}"
git fetch --no-tags --depth=1 origin "${{ github.event.pull_request.base.ref }}" || true
BASE_REF="origin/${{ github.event.pull_request.base.ref }}"
CHANGED=$(git diff --name-only "$BASE_REF" "${{ github.sha }}" || true)
fi

# 3) Final fallback: use files from the last commit(s)
if [ -z "$CHANGED" ]; then
echo "Falling back to last commit changes"
CHANGED=$(git diff --name-only HEAD^ HEAD || git show --name-only --pretty= "" "${{ github.sha }}" || true)
fi

echo "Changed files:\n$CHANGED"

# Normalize changed paths to be relative to the working dir by
# taking the last occurrence of 'src/' (works for nested 'src' paths)
CHANGED_REL=$(printf "%s\n" "$CHANGED" | awk -F'src/' '{ if (NF>1) print "src/"$(NF) ; else print $0 }' | sort -u || true)
# remove empty lines
CHANGED_REL=$(printf "%s\n" "$CHANGED_REL" | sed '/^$/d' || true)
echo "Normalized changes:\n$CHANGED_REL"

# Prefer explicitly changed test files
TEST_FILES=$(printf "%s\n" "$CHANGED_REL" | grep -E 'src/.*\.(test|spec)\.(ts|tsx|js|jsx)' || true)
if [ -n "$TEST_FILES" ]; then
TF=$(echo "$TEST_FILES" | tr '\n' ' ')
echo "Found test files: $TF"
echo "tests=$TF" >> $GITHUB_OUTPUT
exit 0
fi

# Otherwise, map changed source dirs to test globs
DIRS=$(printf "%s\n" "$CHANGED_REL" | grep -E '^src/' || true)
if [ -n "$DIRS" ]; then
PATTERNS=$(printf "%s\n" "$DIRS" | sed -E 's|/[^/]+$||' | sort -u | awk '{print $0"/**/*.test.* "$0"/**/*.spec.*"}' | tr '\n' ' ')
echo "Derived patterns: $PATTERNS"
echo "tests=$PATTERNS" >> $GITHUB_OUTPUT
exit 0
fi

echo "No relevant tests found to run"
echo "tests=none" >> $GITHUB_OUTPUT

- name: Run Vitest (renderer main)
working-directory: app/renderer/src/main
env:
CI: true
run: |
TESTS="${{ steps.test_selection.outputs.tests }}"
if [ "$TESTS" = "none" ]; then
echo "No relevant tests changed, skipping vitest"
exit 0
fi
echo "Running tests: $TESTS"
yarn test:vitest $TESTS --run --reporter verbose
6 changes: 5 additions & 1 deletion app/renderer/src/main/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@
"build-test-memfit": "env-cmd -e memfit,devTool -f ./.env-cmdrc cross-env GENERATE_SOURCEMAP=false react-app-rewired build",
"build-breachtrace": "env-cmd -e breachtrace -f ./.env-cmdrc cross-env GENERATE_SOURCEMAP=false react-app-rewired build",
"test": "react-app-rewired test",
"test:vitest": "vitest",
"eject": "react-app-rewired eject",
"analyzer": "env-cmd -e analyzer -f cross-env react-app-rewired build",
"postinstall": "patch-package",
Expand Down Expand Up @@ -180,7 +181,10 @@
"sass": "^1.54.8",
"sass-loader": "^12.6.0",
"source-map-loader": "^4.0.2",
"webpack-bundle-analyzer": "^4.10.1"
"webpack-bundle-analyzer": "^4.10.1",
"vitest": "0.34.6",
"vite": "4.4.9",
"vite-tsconfig-paths": "3.5.0"
},
"resolutions": {
"//": "See https://github.com/facebook/create-react-app/issues/11773",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import React from "react"
import {render, waitFor, screen} from "@testing-library/react"
import {afterEach, beforeEach, describe, expect, it, vi} from "vitest"

vi.mock("@/components/yakitUI/YakitSpin/YakitSpin", () => {
const React = require("react")
return {
YakitSpin: (props: any) => {
const {spinning, children} = props
return React.createElement(
"div",
{"data-spinning": spinning ? "true" : "false", "data-testid": "yakitspin"},
children
)
}
}
})

const mockGetRemoteValue = vi.fn()
vi.mock("@/utils/kv", () => ({
getRemoteValue: (...args: any[]) => mockGetRemoteValue(...args)
}))

vi.mock("ahooks", () => {
const React = require("react")
return {
useSafeState: (init: any) => React.useState(init)
}
})

let Vitest__Test__: any
beforeAll(async () => {
const mod = await import("@/components/playground/Vitest__Test__")
Vitest__Test__ = mod.Vitest__Test__
})

beforeEach(() => {
mockGetRemoteValue.mockReset()
})

afterEach(() => {
vi.restoreAllMocks()
})

describe("Vitest__Test__ component - loading behavior", () => {
it("sets spinning=false immediately when no editorOperationRecord is provided", async () => {
render(React.createElement(Vitest__Test__))
const spin = screen.getByTestId("yakitspin")
expect(spin).toBeInTheDocument()
expect(spin).toHaveAttribute("data-spinning", "false")
})

it("shows", async () => {
mockGetRemoteValue.mockImplementation(() => Promise.resolve(JSON.stringify({fontSize: 14})))

render(React.createElement(Vitest__Test__, {editorOperationRecord: "my-key"}))

const spin = screen.getByTestId("yakitspin")
expect(spin).toBeInTheDocument()
expect(spin).toHaveAttribute("data-spinning", "true")

await waitFor(() => {
expect(spin).toHaveAttribute("data-spinning", "false")
})
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {describe, it, expect} from "vitest"
import {compareKnowledgeBaseChange} from "@/components/playground/Vitest__Test__/utils"

describe("compareKnowledgeBaseChange", () => {
it("returns true when prev or next is not an array", () => {
expect(compareKnowledgeBaseChange(null, [])).toBe(true)
expect(compareKnowledgeBaseChange([], undefined)).toBe(true)
})

it("detects a deleted item", () => {
const prev = [{ID: "1", name: "a"}, {ID: "2", name: "b"}]
const next = [{ID: "2", name: "b"}]

const res = compareKnowledgeBaseChange(prev as any, next as any)
expect(res).toEqual({delete: prev[0], increase: null})
})

it("detects an increased item", () => {
const prev = [{ID: "1", name: "a"}]
const next = [{ID: "1", name: "a"}, {ID: "2", name: "b"}]

const res = compareKnowledgeBaseChange(prev as any, next as any)
expect(res).toEqual({delete: null, increase: next[1]})
})

it("returns true when there is no change", () => {
const prev = [{ID: "1"}, {ID: "2"}]
const next = [{ID: "1"}, {ID: "2"}]
expect(compareKnowledgeBaseChange(prev as any, next as any)).toBe(true)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import {YakitSpin} from "@/components/yakitUI/YakitSpin/YakitSpin"
import {getRemoteValue} from "@/utils/kv"
import {useSafeState} from "ahooks"
import {FC, useEffect} from "react"

interface VitestTestProps {
editorOperationRecord?: string
}

const Vitest__Test__: FC<VitestTestProps> = (props) => {
const {editorOperationRecord} = props
const [typeLoading, setTypeLoading] = useSafeState<boolean>(true)

useEffect(() => {
setTypeLoading(true)
if (editorOperationRecord) {
getRemoteValue(editorOperationRecord)
.then((data) => {
try {
setTypeLoading(false)
if (!data) return
} catch (error) {
setTypeLoading(false)
fail(error + "")
}
})
.finally(() => {
setTypeLoading(false)
})
} else {
setTypeLoading(false)
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [editorOperationRecord])

return <YakitSpin spinning={typeLoading}>vitest test page</YakitSpin>
}

export {Vitest__Test__}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {KnowledgeBaseItem} from "@/pages/KnowledgeBase/hooks/useKnowledgeBase"

/**
* 对比两个知识库数组,判断新增或删除
* @param prev 上一次的数据
* @param next 当前的数据
*/
const compareKnowledgeBaseChange = (
prev: KnowledgeBaseItem[] | null | undefined,
next: KnowledgeBaseItem[] | null | undefined
): {delete: KnowledgeBaseItem | null; increase: KnowledgeBaseItem | null} | true => {
// 如果任意一方为空,则无法比较,直接返回 true(表示无变化或无法判断)
if (!Array.isArray(prev) || !Array.isArray(next)) return true

const prevMap = new Map(prev.map((item) => [item.ID, item]))
const nextMap = new Map(next.map((item) => [item.ID, item]))

// 查找被删除的对象
const deleted = prev.find((item) => !nextMap.has(item.ID))
if (deleted) return {delete: deleted, increase: null}

// 查找新增的对象
const increased = next.find((item) => !prevMap.has(item.ID))
if (increased) return {delete: null, increase: increased}

// 没有变化
return true
}

export {compareKnowledgeBaseChange}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import {info} from "@/utils/notification"
import {SelectOne} from "@/utils/inputUtil"
import {YakitEditor} from "@/components/yakitUI/YakitEditor/YakitEditor"
import {YakURLTree} from "@/pages/yakURLTree/YakURLTree"
import {TrafficDemo} from "@/components/playground/TrafficDemo"
import {PcapXDemo} from "@/components/playground/PcapXDemo"
import {Vitest__Test__} from "@/components/playground/Vitest__Test__"
import {DemoItemSelectOne} from "@/demoComponents/itemSelect/ItemSelect"
import {RiskTableDemo} from "@/components/playground/RiskTableDemo"
import {ChaosMakerRulesDemo} from "@/components/playground/ChaosMakerRulesDemo"
Expand All @@ -18,7 +18,7 @@ import {JavaDecompilerOperator} from "@/components/playground/javadecompiler/Jav
import {KnowledgeBaseDemo} from "@/components/playground/knowlegeBase/KnowledgeBaseDemo"
import {RagManagerDemo} from "@/components/playground/ragManager/RagManagerDemo"
import {ThirdPartyBinaryManager} from "@/components/playground/thirdPartyBinary/ThirdPartyBinaryManager"
import { EntityRepositoryPage } from "@/components/playground/entityRepository/EntityRepositoryPage"
import {EntityRepositoryPage} from "@/components/playground/entityRepository/EntityRepositoryPage"
export interface DebugMonacoEditorPageProp {}

const TAG = "DEBUG_PLAYGROUND_DEFAULT_MODE"
Expand Down Expand Up @@ -77,6 +77,7 @@ a=1&b=2 Content-Length: a
{value: "rag-manager", label: "RAG 向量存储管理"},
{value: "third-party-binary", label: "第三方应用管理"},
{value: "entity-repository", label: "实体仓库"},
{value: "vitest__test__", label: "Vitest 测试组件"}
]}
formItemStyle={{margin: 0}}
value={mode}
Expand Down Expand Up @@ -112,7 +113,9 @@ a=1&b=2 Content-Length: a
return <ThirdPartyBinaryManager />
case "entity-repository":
return <EntityRepositoryPage />
}
case "vitest__test__":
return <Vitest__Test__ />
}
return <div>NO PLUGIN DEMO</div>
})()}
</AutoCard>
Expand Down
Loading