Skip to content
This repository has been archived by the owner on Oct 6, 2023. It is now read-only.

Commit

Permalink
feat: nested suites
Browse files Browse the repository at this point in the history
  • Loading branch information
Akryum committed Jan 20, 2022
1 parent 6f68b7e commit 8c548eb
Show file tree
Hide file tree
Showing 24 changed files with 773 additions and 421 deletions.
14 changes: 14 additions & 0 deletions examples/demo/dbg.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { setupConfigLoader, toProgramConfig, runAllTests } from '@peeky/test'

const configLoader = await setupConfigLoader()
const config = await configLoader.loadConfig()
await configLoader.destroy()

const { stats: { errorSuiteCount } } = await runAllTests(toProgramConfig(config), {
/* options here */
quickTestFilter: 'nested',
})

if (errorSuiteCount) {
process.exit(1)
}
49 changes: 49 additions & 0 deletions examples/demo/src/nested-suites.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
let count = 0

beforeEach(() => {
count++
})

describe.skip('skipped suite', () => {
it('skipped test', () => {
// Skipped
})
})

describe.todo('todo suite')

describe('nested suite', () => {
it('skipped test', () => {
// Skipped
})

describe.only('only suite', () => {
it('test 1', () => {
expect(count).toBe(1)
})

describe('even more nesting', () => {
beforeEach(() => {
count++
})

it.only('test 2', () => {
expect(count).toBe(3)
})

describe('skipped suite', () => {
it('skipped test', () => {
// Skipped
})
})
})

it('test 3', () => {
expect(count).toBe(4)
})
})
})

it('should count correctly', () => {
expect(count).toBe(5)
})
142 changes: 77 additions & 65 deletions packages/peeky-client/src/features/suite/SuiteItem.vue
Original file line number Diff line number Diff line change
@@ -1,23 +1,54 @@
<script lang="ts">
import gql from 'graphql-tag'
import type { NexusGenFieldTypes } from '@peeky/server/types'
export const testSuiteItemFragment = gql`fragment testSuiteItem on TestSuite {
id
slug
title
status
duration
runTestFile {
id
testFile {
id
relativePath
}
}
}`
export type TestSuiteItem = Pick<NexusGenFieldTypes['TestSuite'],
'id' |
'slug' |
'title' |
'status' |
'duration' |
'runTestFile' |
'children'>
</script>
<script lang="ts" setup>
import TestItem from '../test/TestItem.vue'
import StatusIcon from '../StatusIcon.vue'
import Duration from '../Duration.vue'
import { computed, defineProps } from 'vue'
import { compareStatus } from '../../util/status'
import { defineProps, PropType } from 'vue'
const props = defineProps({
suite: {
type: Object,
type: Object as PropType<TestSuiteItem>,
required: true,
},
run: {
type: Object,
type: Object as PropType<NexusGenFieldTypes['Run']>,
required: true,
},
search: {
type: Object,
type: Object as PropType<{
searchReg: RegExp | null
filterFailed: boolean
}>,
default: null,
},
Expand All @@ -26,74 +57,55 @@ const props = defineProps({
required: true,
},
})
const hasSearch = computed(() => !!(props.search?.searchReg || props.search?.filterFailed))
const filteredTests = computed(() => {
let tests = props.suite.tests
if (props.search?.filterFailed) {
tests = tests.filter(t => t.status === 'error')
}
if (props.search?.searchReg) {
tests = tests.filter(t => t.title.search(props.search.searchReg) !== -1)
}
return tests
})
const sortedTests = computed(() => filteredTests.value.slice().sort((a, b) => compareStatus(a.status, b.status)))
</script>

<template>
<div
v-if="!hasSearch || filteredTests.length"
class="mb-2"
>
<div
class="flex items-center space-x-2 h-8 px-3"
:style="{
paddingLeft: `${depth * 16 + 6}px`,
}"
>
<StatusIcon
:status="suite.status"
class="w-4 h-4 flex-none"
/>
<span
class="flex-1 truncate py-1"
:class="{
'opacity-60': suite.status === 'skipped',
<div>
<template v-if="depth >= 0">
<div
class="flex items-center space-x-2 h-8 px-3"
:style="{
paddingLeft: `${depth * 12 + 6}px`,
}"
>
{{ suite.title }}
</span>
<Duration
:duration="suite.duration"
class="flex-none"
/>
</div>

<div
v-if="!suite.tests.length"
class="bg-gray-50 dark:bg-gray-900 text-gray-600 dark:text-gray-400 m-1 rounded relative text-sm"
>
<div class="absolute left-10 -top-1 w-3 h-3 transform rotate-45 bg-gray-100 dark:bg-gray-900" />

<div class="relative px-2 py-1">
😿️ No tests found in this suite
<StatusIcon
:status="suite.status"
class="w-4 h-4 flex-none"
/>
<span
class="flex-1 truncate py-1"
:class="{
'opacity-60': suite.status === 'skipped',
}"
>
{{ suite.title }}
</span>
<Duration
:duration="suite.duration"
class="flex-none"
/>
</div>
</div>
</template>

<div>
<TestItem
v-for="test of sortedTests"
:key="test.id"
:test="test"
:suite="suite"
:depth="depth + 1"
/>
<template
v-for="child of suite.children"
:key="child.id"
>
<SuiteItem
v-if="child.__typename === 'TestSuite'"
:suite="child"
:run="run"
:search="search"
:depth="depth + 1"
/>
<TestItem
v-else
:test="child"
:suite="suite"
:depth="depth + 1"
/>
</template>
</div>
</div>
</template>
57 changes: 51 additions & 6 deletions packages/peeky-client/src/features/suite/SuitesView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,69 @@ import BaseInput from '../BaseInput.vue'
import SuiteItem from './SuiteItem.vue'
import { SearchIcon } from '@zhuowenli/vue-feather-icons'
import { compareStatus } from '../../util/status'
import { NexusGenFieldTypes } from '@peeky/server/types'
type TestSuite = Omit<NexusGenFieldTypes['TestSuite'], 'children'> & {
__typename: 'TestSuite'
children: (TestSuite | Test)[]
}
type Test = NexusGenFieldTypes['Test'] & {
__typename: 'Test'
}
const props = defineProps<{
suites: any[]
suites: TestSuite[]
run: any
}>()
// Filtering
const searchText = ref('')
const searchReg = computed(() => searchText.value ? new RegExp(searchText.value, 'gi') : null)
const failedTestCount = computed(() => {
return props.suites.reduce((sum, suite) => {
return sum + suite.tests.reduce((sum, test) => {
return sum + (test.status === 'error' ? 1 : 0)
return sum + suite.children.reduce((sum, child) => {
return sum + (child.__typename === 'Test' && child.status === 'error' ? 1 : 0)
}, 0)
}, 0)
})
const sortedSuites = computed(() => props.suites.slice().sort((a, b) => compareStatus(a.status, b.status)))
function isChildMatching (item: TestSuite | Test) {
// If one of the nested is in 'error', the suite is also in 'error'
if (filterFailed.value && item.status !== 'error') return false
// Title search
if (searchReg.value && item.title.search(searchReg.value) === -1) return false
return true
}
function isNestedMatching (item: TestSuite | Test) {
if (isChildMatching(item)) return true
if (item.__typename === 'TestSuite' && item.children.some(child => isNestedMatching(child))) return true
return false
}
// Tree
const tree = computed(() => {
if (!props.suites.length) return []
function processSuite (item: TestSuite): TestSuite {
return {
...item,
children: item.children.map(child => {
if (child.__typename === 'TestSuite') {
return processSuite(props.suites.find(suite => suite.id === child.id) as TestSuite)
}
return child
}).filter(child => isNestedMatching(child))
}
}
const rootSuites = props.suites.filter(s => s.root)
return rootSuites.map(suite => processSuite(suite))
})
</script>

<template>
Expand Down Expand Up @@ -68,15 +113,15 @@ const sortedSuites = computed(() => props.suites.slice().sort((a, b) => compareS

<div class="flex-1 overflow-y-auto">
<SuiteItem
v-for="suite of sortedSuites"
v-for="suite of tree"
:key="suite.id"
:suite="suite"
:run="run"
:search="{
searchReg,
filterFailed,
}"
:depth="0"
:depth="-1"
/>

<div
Expand Down
25 changes: 11 additions & 14 deletions packages/peeky-client/src/features/test-file/TestFileAllView.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts" setup>
import SuitesView from '../suite/SuitesView.vue'
import { testItemFragment } from '../test/TestItem.vue'
import { testSuiteItemFragment } from '../suite/SuiteItem.vue'
import { useQuery, useResult } from '@vue/apollo-composable'
import gql from 'graphql-tag'
import { useRoute } from 'vue-router'
Expand All @@ -9,30 +10,26 @@ const route = useRoute()
const runTestFileAllSuiteFragment = gql`
fragment runTestFileAllSuite on TestSuite {
id
slug
title
status
duration
runTestFile {
id
testFile {
...testSuiteItem
root
children {
...on TestSuite {
id
relativePath
}
}
tests {
...testItem
...on Test {
...testItem
}
}
}
${testSuiteItemFragment}
${testItemFragment}
`
const { result, subscribeToMore, onResult } = useQuery(() => route.params.runId !== 'last-run' ? gql`
query testFileAllView ($runId: ID!) {
run (id: $runId) {
id
testSuites {
testSuites: allTestSuites {
...runTestFileAllSuite
}
}
Expand All @@ -42,7 +39,7 @@ const { result, subscribeToMore, onResult } = useQuery(() => route.params.runId
query testFileAllViewLastRun {
run: lastRun {
id
testSuites {
testSuites: allTestSuites {
...runTestFileAllSuite
}
}
Expand Down
Loading

0 comments on commit 8c548eb

Please sign in to comment.