Skip to content

Commit 9fa3a79

Browse files
committed
Add "Well-Designed Assertion API"
1 parent 8f6228d commit 9fa3a79

File tree

4 files changed

+274
-34
lines changed

4 files changed

+274
-34
lines changed

.vitepress/theme/style.css

Lines changed: 56 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -231,59 +231,81 @@
231231
font-size: 14px;
232232
}
233233

234-
/* Home page code demo section */
235-
.home-code-demo {
236-
max-width: 800px;
237-
margin: 32px auto 0;
234+
/* Home page feature section */
235+
.home-feature {
236+
margin: 80px auto 0;
238237
padding: 0 24px;
239238
}
240239

241-
/* Home page text sections */
242-
.home-section {
243-
max-width: 800px;
244-
margin: 80px auto 0;
245-
padding: 0 24px;
246-
text-align: center;
240+
.home-feature > h2 {
241+
font-size: 32px !important;
242+
font-weight: 400 !important;
243+
margin-bottom: 32px !important;
244+
color: var(--vp-c-text-1) !important;
245+
border: none !important;
246+
text-align: center !important;
247247
}
248248

249-
.home-section-title {
250-
font-size: 32px;
251-
font-weight: 600;
252-
margin-bottom: 16px;
253-
color: var(--vp-c-text-1);
249+
/* Home page two-column feature row */
250+
.home-feature-row {
251+
display: flex;
252+
gap: 48px;
253+
align-items: flex-start;
254+
}
255+
256+
.home-feature-text {
257+
flex: 1;
258+
text-align: left;
254259
}
255260

256-
.home-section-text {
257-
font-size: 18px;
261+
.home-feature-text p {
262+
font-size: 17px;
258263
line-height: 1.7;
259264
color: var(--vp-c-text-2);
260-
max-width: 600px;
261-
margin: 0 auto;
265+
margin: 0 0 16px;
262266
}
263267

264-
.home-section-text code {
268+
.home-feature-text code {
265269
background: var(--vp-c-bg-soft);
266270
padding: 2px 6px;
267271
border-radius: 4px;
268-
font-size: 16px;
272+
font-size: 15px;
269273
color: var(--vp-c-brand-1);
270274
}
271275

272-
@media (max-width: 960px) {
273-
.home-code-demo {
274-
max-width: 100%;
275-
margin-top: 24px;
276-
}
276+
.home-feature-text ul {
277+
list-style: none;
278+
padding: 0;
279+
margin: 16px 0;
280+
}
277281

278-
.home-section {
279-
margin-top: 60px;
280-
}
282+
.home-feature-text li {
283+
font-size: 16px;
284+
line-height: 1.7;
285+
color: var(--vp-c-text-2);
286+
padding: 4px 0;
287+
}
281288

282-
.home-section-title {
283-
font-size: 26px;
284-
}
289+
.home-feature-text li::before {
290+
content: "•";
291+
color: var(--vp-c-brand-1);
292+
margin-right: 8px;
293+
}
294+
295+
.home-feature-code {
296+
flex: 1;
297+
min-width: 0;
298+
width: 100%;
299+
}
285300

286-
.home-section-text {
287-
font-size: 16px;
301+
.home-feature-code .code-tabs-ide {
302+
margin: 0;
303+
width: 100%;
304+
}
305+
306+
@media (max-width: 960px) {
307+
.home-feature-row {
308+
flex-direction: column;
309+
gap: 32px;
288310
}
289311
}

index.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,106 @@ features:
3939
details: Separate Assert (now) and Expect (later) facades with pipe assertions for type-safe checks.
4040
---
4141

42+
<script setup>
43+
const assertTabs = [
44+
{ name: 'Assert.php', slot: 'assert', icon: 'testo' },
45+
{ name: 'Expect.php', slot: 'expect', icon: 'testo' },
46+
{ name: 'Exception.php', slot: 'expectException', icon: 'testo' },
47+
{ name: 'Attributes.php', slot: 'expectAttr', icon: 'testo-class' },
48+
]
49+
</script>
50+
51+
<div class="home-feature">
52+
53+
## Well-Designed Assertion API
54+
55+
<div class="home-feature-row">
56+
<div class="home-feature-text">
57+
58+
Assertion functions are split into semantic groups:
59+
60+
- `Assert::` facade — assertions, executed immediately
61+
- `Expect::` facade — expectations, deferred until test completion
62+
63+
Pipe syntax with type grouping keeps code concise and type-safe.
64+
65+
</div>
66+
<div class="home-feature-code">
67+
<CodeTabs :tabs="assertTabs">
68+
69+
<template #assert>
70+
71+
```php
72+
use Testo\Assert;
73+
74+
// Pipe assertions — grouped by type
75+
Assert::string($email)->contains('@');
76+
Assert::int($age)->greaterThan(18);
77+
Assert::file('config.php')->exists();
78+
79+
Assert::array($order->items)
80+
->allOf(Item::class)
81+
->hasCount(3);
82+
```
83+
84+
</template>
85+
86+
<template #expect>
87+
88+
```php
89+
use Testo\Expect;
90+
91+
// ORM should stay in memory
92+
Expect::leaks($orm);
93+
94+
// Test fails if entities are not cleaned up
95+
Expect::notLeaks(...$entities);
96+
97+
// Output validation
98+
Expect::output()->contains('Done');
99+
```
100+
101+
</template>
102+
103+
<template #expectException>
104+
105+
```php
106+
// Have you seen this anywhere else?
107+
Expect::exception(ValidationException::class)
108+
->fromMethod(Service::class, 'validateInput')
109+
->withMessage('Invalid input')
110+
->withPrevious(
111+
WrongTypeException::class,
112+
static fn (ExpectedException $e) => $e
113+
->withCode(42)
114+
->withMessage('Field "age" must be integer.'),
115+
);
116+
```
117+
118+
</template>
119+
120+
<template #expectAttr>
121+
122+
```php
123+
/**
124+
* You can use attributes for exception expectations
125+
*/
126+
#[ExpectException(ValidationException::class)]
127+
public function testInvalidInput(): void
128+
{
129+
$input = ['age' => 'twenty'];
130+
131+
$this->service->validateInput($input);
132+
}
133+
```
134+
135+
</template>
136+
137+
</CodeTabs>
138+
</div>
139+
</div>
140+
</div>
141+
42142
<div class="sponsors-section">
43143
<h2 class="sponsors-title">Sponsored by</h2>
44144
<div class="sponsors-grid">

ru/index.md

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,106 @@ features:
3939
details: Раздельные фасады Assert (сейчас) и Expect (потом) с пайповыми ассертами для типобезопасных проверок.
4040
---
4141

42+
<script setup>
43+
const assertTabs = [
44+
{ name: 'Assert.php', slot: 'assert', icon: 'testo' },
45+
{ name: 'Expect.php', slot: 'expect', icon: 'testo' },
46+
{ name: 'Exception.php', slot: 'expectException', icon: 'testo' },
47+
{ name: 'Attributes.php', slot: 'expectAttr', icon: 'testo-class' },
48+
]
49+
</script>
50+
51+
<div class="home-feature">
52+
53+
## Продуманный API ассертов
54+
55+
<div class="home-feature-row">
56+
<div class="home-feature-text">
57+
58+
Функции проверок разбиты на семантические группы:
59+
60+
- Фасад `Assert::` — утверждения, выполняются сразу
61+
- Фасад `Expect::` — ожидания, откладываются до завершения теста
62+
63+
Пайповый синтаксис с группировкой по типу делает код лаконичным и типобезопасным.
64+
65+
</div>
66+
<div class="home-feature-code">
67+
<CodeTabs :tabs="assertTabs">
68+
69+
<template #assert>
70+
71+
```php
72+
use Testo\Assert;
73+
74+
// Пайповые ассерты — группировка по типу
75+
Assert::string($email)->contains('@');
76+
Assert::int($age)->greaterThan(18);
77+
Assert::file('config.php')->exists();
78+
79+
Assert::array($order->items)
80+
->allOf(Item::class)
81+
->hasCount(3);
82+
```
83+
84+
</template>
85+
86+
<template #expect>
87+
88+
```php
89+
use Testo\Expect;
90+
91+
// ORM должен остаться в памяти
92+
Expect::leaks($orm);
93+
94+
// Тест упадёт, если сущности не будут подчищены
95+
Expect::notLeaks(...$entitis);
96+
97+
// Валидация вывода
98+
Expect::output()->contains('Done');
99+
```
100+
101+
</template>
102+
103+
<template #expectException>
104+
105+
```php
106+
// Где вы ещё такое видели?
107+
Expect::exception(ValidationException::class)
108+
->fromMethod(Service::class, 'validateInput')
109+
->withMessage('Invalid input')
110+
->withPrevious(
111+
WrongTypeException::class,
112+
static fn (ExpectedException $e) => $e
113+
->withCode(42)
114+
->withMessage('Field "age" must be integer.'),
115+
);
116+
```
117+
118+
</template>
119+
120+
<template #expectAttr>
121+
122+
```php
123+
/**
124+
* Можно использовать атрибуты для ожидания исключений
125+
*/
126+
#[ExpectException(ValidationException::class)]
127+
public function testInvalidInput(): void
128+
{
129+
$input = ['age' => 'twenty'];
130+
131+
$this->service->validateInput($input);
132+
}
133+
```
134+
135+
</template>
136+
137+
</CodeTabs>
138+
</div>
139+
</div>
140+
</div>
141+
42142
<div class="sponsors-section">
43143
<h2 class="sponsors-title">Спонсоры</h2>
44144
<div class="sponsors-grid">

uno.config.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { defineConfig, presetUno, presetAttributify } from 'unocss'
2+
3+
export default defineConfig({
4+
presets: [
5+
presetUno(),
6+
presetAttributify(),
7+
],
8+
shortcuts: {
9+
'brand-text': 'text-[var(--vp-c-brand-1)]',
10+
},
11+
theme: {
12+
breakpoints: {
13+
sm: '640px',
14+
md: '960px',
15+
lg: '1152px',
16+
},
17+
},
18+
})

0 commit comments

Comments
 (0)