| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168 |
- <?php
- use App\Facades\RedisCache;
- use App\Models\Discussion;
- use App\Services\PaliContentService;
- use Illuminate\Foundation\Testing\RefreshDatabase;
- use Illuminate\Support\Str;
- uses(RefreshDatabase::class);
- /**
- * 阅读页批注内嵌:type='commentary'(注释书对应,带出处)与 type='note'(普通边注,无出处)。
- */
- /** 建 channel + 一句根本译文(book 9101 第 1 段),返回 [channel uid, 句子] */
- function makeAnnotatedSentence(): array
- {
- $channel = makeChannel(makeStudio('anno-owner'), 'anno channel');
- $sentence = makeSentence($channel, 9101, 1, 1, 'first sentence');
- return [$channel, $sentence];
- }
- function makeAnnotation(string $resId, string $type, string $content, int $posEnd): Discussion
- {
- $discussion = new Discussion;
- $discussion->forceFill([
- 'res_id' => $resId,
- 'res_type' => 'sentence',
- 'type' => $type,
- 'title' => $content,
- 'content' => $content,
- 'content_type' => 'markdown',
- 'editor_uid' => makeStudio('anno-'.Str::random(6)),
- 'pos_start' => 0,
- 'pos_end' => $posEnd,
- 'quote_exact' => 'first',
- ])->save();
- return $discussion;
- }
- it('injects a commentary annotation with a cite link', function () {
- [$channel, $sentence] = makeAnnotatedSentence();
- // 义注句:commentary 的 content 是它的句子模板,渲染成它在同 channel 的译文
- makeSentence($channel, 9102, 7, 3, '义注这么说');
- makeAnnotation($sentence->uid, 'commentary', '{{9102-7-3-3}}', 5);
- $display = $this->getJson("/api/v3/tipitaka-reading/{$channel}?book=9101¶=1")
- ->assertOk()
- ->json('data.0.display');
- expect($display)
- ->toContain('义注这么说')
- ->toContain('<cite class="anno-jump" data-book="9102" data-para="7" data-start="3" data-end="3">义注</cite>');
- });
- it('injects a plain note annotation without a cite link', function () {
- [$channel, $sentence] = makeAnnotatedSentence();
- // 普通边注:content 就是注解正文本身
- makeAnnotation($sentence->uid, 'note', '这里的 dassana 指见到佛陀。', 5);
- $display = $this->getJson("/api/v3/tipitaka-reading/{$channel}?book=9101¶=1")
- ->assertOk()
- ->json('data.0.display');
- // 正文照原样进 sidenote,紧跟 </span> 收尾——没有 <cite> 出处
- expect($display)
- ->toContain('这里的 dassana 指见到佛陀。</span>')
- ->not->toContain('anno-jump');
- });
- it('injects both kinds on the same sentence, note first at the same position', function () {
- [$channel, $sentence] = makeAnnotatedSentence();
- makeSentence($channel, 9102, 7, 3, '义注这么说');
- makeAnnotation($sentence->uid, 'commentary', '{{9102-7-3-3}}', 5);
- makeAnnotation($sentence->uid, 'note', '译者按。', 5);
- $display = $this->getJson("/api/v3/tipitaka-reading/{$channel}?book=9101¶=1")
- ->assertOk()
- ->json('data.0.display');
- expect(mb_strpos($display, '译者按。'))->toBeLessThan(mb_strpos($display, '义注这么说'));
- });
- it('drops the paragraph cache when either kind of annotation changes', function () {
- [$channel, $sentence] = makeAnnotatedSentence();
- $url = "/api/v3/tipitaka-reading/{$channel}?book=9101¶=1";
- $this->getJson($url)->assertOk();
- $key = PaliContentService::paragraphCacheKey(9101, 1, $channel, 'html');
- expect(RedisCache::has($key))->toBeTrue();
- makeAnnotation($sentence->uid, 'note', '译者按。', 5);
- expect(RedisCache::has($key))->toBeFalse();
- expect($this->getJson($url)->json('data.0.display'))->toContain('译者按。');
- });
- /* ------------------------------------------------------------------ glossary 与 issue */
- function makeAnchored(string $resId, string $type, array $attrs): Discussion
- {
- $discussion = new Discussion;
- $discussion->forceFill(array_merge([
- 'res_id' => $resId,
- 'res_type' => 'sentence',
- 'type' => $type,
- 'title' => 't',
- 'content' => 'body',
- 'content_type' => 'markdown',
- 'editor_uid' => makeStudio('anno-'.Str::random(6)),
- ], $attrs))->save();
- return $discussion;
- }
- function readFirstParagraph(string $channel): string
- {
- return test()->getJson("/api/v3/tipitaka-reading/{$channel}?book=9101¶=1")->assertOk()->json('data.0.display');
- }
- it('replaces a glossary anchor with a term reference', function () {
- [$channel, $sentence] = makeAnnotatedSentence(); // 'first sentence'
- makeAnchored($sentence->uid, 'glossary', ['title' => 'paṭhama', 'pos_start' => 0, 'pos_end' => 5, 'quote_exact' => 'first']);
- expect(readFirstParagraph($channel))
- ->toContain("data-term='paṭhama'")
- ->toContain('>first</span> sentence');
- });
- it('highlights issues, merging overlaps, and marks issues without a position at the sentence end', function () {
- [$channel, $sentence] = makeAnnotatedSentence();
- $a = makeAnchored($sentence->uid, 'issue', ['severity' => 'suggestion', 'pos_start' => 0, 'pos_end' => 8, 'quote_exact' => 'first se']);
- $b = makeAnchored($sentence->uid, 'issue', ['severity' => 'error', 'pos_start' => 6, 'pos_end' => 14, 'quote_exact' => 'sentence']);
- $c = makeAnchored($sentence->uid, 'issue', ['severity' => 'warning']);
- expect(readFirstParagraph($channel))
- ->toContain('<span class="anno-issue anno-issue-error" data-discussion-ids="'.$a->id.','.$b->id.'" data-severity="error">first sentence</span>')
- ->toContain('<span class="anno-issue-mark anno-issue-warning" data-discussion-ids="'.$c->id.'"');
- });
- it('relocates an issue by its quote when the position is stale', function () {
- [$channel, $sentence] = makeAnnotatedSentence();
- makeAnchored($sentence->uid, 'issue', ['severity' => 'error', 'pos_start' => 0, 'pos_end' => 3, 'quote_exact' => 'sentence']);
- expect(readFirstParagraph($channel))->toContain('data-severity="error">sentence</span>');
- });
- it('does not inject closed issues or glossaries, and drops the cache when one is closed', function () {
- [$channel, $sentence] = makeAnnotatedSentence();
- makeAnchored($sentence->uid, 'glossary', ['title' => 'x', 'pos_start' => 0, 'pos_end' => 5, 'quote_exact' => 'first', 'status' => 'close']);
- $issue = makeAnchored($sentence->uid, 'issue', ['severity' => 'error', 'pos_start' => 6, 'pos_end' => 14]);
- expect(readFirstParagraph($channel))->toContain('anno-issue')->not->toContain('term-ref');
- $issue->forceFill(['status' => 'close'])->save(); // 关闭会清缓存
- expect(readFirstParagraph($channel))->not->toContain('anno-issue');
- });
- it('leaves issue markup out of plain text output but keeps the glossary term', function () {
- [$channel, $sentence] = makeAnnotatedSentence();
- makeAnchored($sentence->uid, 'issue', ['severity' => 'error']);
- makeAnchored($sentence->uid, 'glossary', ['title' => 'paṭhama', 'pos_start' => 0, 'pos_end' => 5]);
- $text = app(PaliContentService::class)->readParagraph(9101, 1, $channel, 'text')['display'];
- expect($text)->toBe('first sentence');
- });
|