Skip to content

Commit 3c4f564

Browse files
committed
Renamed Formatter to Md and improved test coverage
1 parent 20a613a commit 3c4f564

6 files changed

Lines changed: 149 additions & 43 deletions

File tree

src/Blocks/Virtual/CodeBlock.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace SlackPhp\BlockKit\Blocks\Virtual;
66

77
use SlackPhp\BlockKit\Blocks\{Context, Section};
8-
use SlackPhp\BlockKit\Kit;
8+
use SlackPhp\BlockKit\Md;
99

1010
class CodeBlock extends VirtualBlock
1111
{
@@ -36,7 +36,7 @@ public function caption(string $caption): self
3636

3737
public function code(string $code): self
3838
{
39-
$code = Kit::formatter()->codeBlock($code);
39+
$code = Md::new()->codeBlock($code);
4040
$this->append(new Section($code));
4141

4242
return $this;

src/Kit.php

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,11 @@ public static function twoColumnTable(
542542
#endregion
543543

544544
#region Other tools and helpers
545+
public static function md(): Md
546+
{
547+
return new Md();
548+
}
549+
545550
/**
546551
* @param array<string, mixed> $data
547552
*/
@@ -550,11 +555,6 @@ public static function privateMetadata(array $data = []): PrivateMetadata
550555
return new PrivateMetadata($data);
551556
}
552557

553-
public static function formatter(): Formatter
554-
{
555-
return new Formatter();
556-
}
557-
558558
public static function preview(Surfaces\Surface $surface): string
559559
{
560560
return Previewer::new()->preview($surface);

src/Formatter.php renamed to src/Md.php

Lines changed: 24 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@
1717
use function time;
1818

1919
/**
20-
* Formatter consists of text formatting helpers to support the use of Slack's Mrkdwn format in messages, modals, etc.
20+
* Md consists of text formatting helpers to support the use of Slack's Mrkdwn format in messages, modals, etc.
2121
*
2222
* Some characters ("<", ">", "&") should be escaped when they are not a part of formatting that requires those
2323
* characters. The formatting helpers in this class automatically escape input in situations where the text is place
2424
* within angle brackets, such as with dates and links.
2525
*/
26-
final class Formatter
26+
final class Md
2727
{
28+
private static ?self $instance = null;
29+
2830
public const DATE = '{date}';
2931
public const DATE_LONG = '{date_long}';
3032
public const DATE_LONG_PRETTY = '{date_long_pretty}';
@@ -37,7 +39,7 @@ final class Formatter
3739

3840
public static function new(): self
3941
{
40-
return new self();
42+
return (self::$instance ??= new self());
4143
}
4244

4345
/**
@@ -72,7 +74,7 @@ public function sub(string $text, array $values): string
7274
return strtr($text, $replacements);
7375
}
7476

75-
//region Helpers for @here, @channel, and @everyone mentions.
77+
#region Helpers for @here, @channel, and @everyone mentions.
7678
public function atChannel(): string
7779
{
7880
return '<!channel>';
@@ -87,9 +89,9 @@ public function atHere(): string
8789
{
8890
return '<!here>';
8991
}
90-
//endregion
92+
#endregion
9193

92-
//region Helpers for mentioning/linking specific channels, users, or user groups.
94+
#region Helpers for mentioning/linking specific channels, users, or user groups.
9395
public function channel(string $id): string
9496
{
9597
return "<#{$id}>";
@@ -104,9 +106,9 @@ public function userGroup(string $id): string
104106
{
105107
return "<!subteam^{$id}>";
106108
}
107-
//endregion
109+
#endregion
108110

109-
//region Helpers for basic text formatting (B/I/S) and links.
111+
#region Helpers for basic text formatting (B/I/S) and links.
110112
public function bold(string $text): string
111113
{
112114
return "*{$text}*";
@@ -129,43 +131,39 @@ public function strike(string $text): string
129131

130132
public function link(string $url, ?string $text = null): string
131133
{
132-
return isset($text) ? "<{$this->escape($url)}|{$this->escape($text)}>" : "<{$this->escape($url)}>";
134+
return isset($text) ? "<{$url}|{$this->escape($text)}>" : "<{$url}>";
133135
}
134136

135137
public function emailLink(string $email, ?string $text = null): string
136138
{
137139
return $this->link("mailto:{$email}", $text);
138140
}
139-
//endregion
141+
#endregion
140142

141-
//region Helpers for multi-line content blocks like lists and quotes.
143+
#region Helpers for multi-line content blocks like lists and quotes.
142144
/**
143145
* @param array<string>|string $lines
144-
* @return string
145146
*/
146147
public function blockQuote(array|string $lines): string
147148
{
148149
return $this->lines($this->explode($lines), '> ', false);
149150
}
150151

152+
public function codeBlock(string $text): string
153+
{
154+
return "```\n{$this->escape($text)}\n```";
155+
}
156+
151157
/**
152158
* @param array<string>|string $items
153-
* @param string $bullet
154-
* @return string
155159
*/
156160
public function bulletedList(array|string $items, string $bullet = ''): string
157161
{
158162
return $this->lines($this->explode($items), "{$bullet} ");
159163
}
160164

161-
public function codeBlock(string $text): string
162-
{
163-
return "```\n{$this->escape($text)}\n```";
164-
}
165-
166165
/**
167166
* @param array<string>|string $items
168-
* @return string
169167
*/
170168
public function numberedList(array|string $items): string
171169
{
@@ -182,9 +180,6 @@ public function numberedList(array|string $items): string
182180
* Optionally applies a prefix to each line. You can use a closure if the prefix varies per line.
183181
*
184182
* @param array<string> $lines
185-
* @param string|callable|null $prefix
186-
* @param bool $filter
187-
* @return string
188183
*/
189184
public function lines(array $lines, string|callable|null $prefix = null, bool $filter = true): string
190185
{
@@ -194,10 +189,8 @@ public function lines(array $lines, string|callable|null $prefix = null, bool $f
194189
};
195190
}
196191

197-
if (is_callable($prefix)) {
192+
if (!is_null($prefix)) {
198193
$lines = array_map($prefix, $lines);
199-
} elseif (!is_null($prefix)) {
200-
throw new Exception('Formatter::lines given invalid prefix argument');
201194
}
202195

203196
if ($filter) {
@@ -208,17 +201,16 @@ public function lines(array $lines, string|callable|null $prefix = null, bool $f
208201

209202
return implode("\n", $lines) . "\n";
210203
}
211-
//endregion
204+
#endregion
212205

213-
//region Helpers for formatting dates and times.
206+
#region Helpers for formatting dates and times.
214207
/**
215208
* Formats a timestamp as a date in mrkdwn.
216209
*
217210
* @param int|null $timestamp Timestamp to format. Defaults to now.
218211
* @param string $format Format name supported by Slack. Defaults to "{date}".
219212
* @param string|null $fallback Fallback text for old Slack clients. Defaults to an ISO-formatted timestamp.
220213
* @param string|null $link URL, if the date is to act as a link.
221-
* @return string
222214
* @see https://api.slack.com/reference/surfaces/formatting#date-formatting
223215
*/
224216
public function date(
@@ -243,7 +235,6 @@ public function date(
243235
* @param string $format Format name supported by Slack. Defaults to "{time}".
244236
* @param string|null $fallback Fallback text for old Slack clients. Defaults to an ISO-formatted timestamp.
245237
* @param string|null $link URL, if the time is to act as a link.
246-
* @return string
247238
*/
248239
public function time(
249240
?int $timestamp = null,
@@ -253,7 +244,7 @@ public function time(
253244
): string {
254245
return $this->date($timestamp, $format, $fallback, $link);
255246
}
256-
//endregion
247+
#endregion
257248

258249
/**
259250
* Ensures the provided items are an array.
@@ -265,12 +256,10 @@ public function time(
265256
*/
266257
private function explode(array|string $items): array
267258
{
268-
if (is_string($items)) {
269-
return explode("\n", $items);
270-
} elseif (is_array($items)) {
259+
if (is_array($items)) {
271260
return $items;
272261
}
273262

274-
throw new Exception('Formatter::explode given invalid items argument');
263+
return explode("\n", $items);
275264
}
276265
}

tests/Functional/CreateTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ public function testThatAllKitComponentMethodsReturnComponents(): void
161161
'fieldsFromMap',
162162
'fieldsFromPairs',
163163
'privateMetadata',
164-
'formatter',
164+
'md',
165165
'preview',
166166
'hydrate',
167167
];

tests/MdTest.php

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace SlackPhp\BlockKit\Tests;
4+
5+
use SlackPhp\BlockKit\Md;
6+
7+
/**
8+
* @covers \SlackPhp\BlockKit\Md
9+
*/
10+
class MdTest extends TestCase
11+
{
12+
public function testCanDoSimpleTextFormatting(): void
13+
{
14+
$f = Md::new();
15+
$this->assertEquals('*hello*', $f->bold('hello'));
16+
$this->assertEquals('_hello_', $f->italic('hello'));
17+
$this->assertEquals('~hello~', $f->strike('hello'));
18+
$this->assertEquals('`hello`', $f->code('hello'));
19+
}
20+
21+
public function testCanDoEntityReferenceFormatting(): void
22+
{
23+
$f = Md::new();
24+
$this->assertEquals('<!channel>', $f->atChannel());
25+
$this->assertEquals('<!everyone>', $f->atEveryone());
26+
$this->assertEquals('<!here>', $f->atHere());
27+
$this->assertEquals('<#C01>', $f->channel('C01'));
28+
$this->assertEquals('<@U01>', $f->user('U01'));
29+
$this->assertEquals('<!subteam^G01>', $f->userGroup('G01'));
30+
}
31+
32+
public function testCanInterpolateAndEscapeText(): void
33+
{
34+
$f = Md::new();
35+
$text = $f->escape($f->sub('There is {name} & John.', ['name' => 'Jim']));
36+
$this->assertEquals('There is Jim &amp; John.', $text);
37+
}
38+
39+
public function testCanFormatLinks(): void
40+
{
41+
$f = Md::new();
42+
$this->assertEquals(
43+
'<https://slack.com|slack website>',
44+
$f->link('https://slack.com', 'slack website'),
45+
);
46+
$this->assertEquals(
47+
'<https://slack.com|&lt;slack&amp;website&gt;>',
48+
$f->link('https://slack.com', '<slack&website>'),
49+
);
50+
$this->assertEquals(
51+
'<mailto:noreply@slack.com|email slack>',
52+
$f->emailLink('noreply@slack.com', 'email slack'),
53+
);
54+
}
55+
56+
public function testCanDoComplexFormatting(): void
57+
{
58+
$f = Md::new();
59+
$this->assertEquals(
60+
"> hello\n> world\n",
61+
$f->blockQuote("hello\nworld"),
62+
);
63+
$this->assertEquals(
64+
"```\necho \"hello\";\n```",
65+
$f->codeBlock('echo "hello";'),
66+
);
67+
$this->assertEquals(
68+
"• a\n• b\n• c\n",
69+
$f->bulletedList(['a', 'b', 'c']),
70+
);
71+
$this->assertEquals(
72+
"1. a\n2. b\n3. c\n",
73+
$f->numberedList(['a', 'b', 'c']),
74+
);
75+
76+
$time = time();
77+
$this->assertEquals(
78+
"<!date^{$time}^{time}^http:/slack.com|foo>",
79+
$f->time($time, fallback: 'foo', link: 'http:/slack.com'),
80+
);
81+
}
82+
}

tests/TypeTest.php

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
namespace SlackPhp\BlockKit\Tests;
4+
5+
use SlackPhp\BlockKit\Blocks\Section;
6+
use SlackPhp\BlockKit\{Exception, Kit, Type};
7+
use Throwable;
8+
9+
/**
10+
* @covers \SlackPhp\BlockKit\Type
11+
*/
12+
class TypeTest extends TestCase
13+
{
14+
public function testCanMapDefinedElementClassToADefinedType(): void
15+
{
16+
$this->assertEquals(Type::SECTION, Type::fromClass(Section::class));
17+
}
18+
19+
public function testThrowsErrorIfMappingClassesNotRegisteredInTypeMaps(): void
20+
{
21+
$this->expectException(Exception::class);
22+
Type::fromClass(Kit::class);
23+
}
24+
25+
public function testCanMapDefinedElementTypeToADefinedClass(): void
26+
{
27+
$this->assertEquals(Section::class, Type::SECTION->toClass());
28+
}
29+
30+
public function testThrowsErrorIfMappingTypesNotRegisteredInTypeMaps(): void
31+
{
32+
$this->expectException(Throwable::class);
33+
Type::fromValue('shoe');
34+
}
35+
}

0 commit comments

Comments
 (0)