LibraryTipitakaChannelTest.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. <?php
  2. use App\Models\BookTitle;
  3. use App\Models\Channel;
  4. use App\Models\PaliText;
  5. use App\Models\ProgressChapter;
  6. use App\Models\Tag;
  7. use App\Models\TagMap;
  8. use Illuminate\Foundation\Testing\RefreshDatabase;
  9. use Illuminate\Support\Str;
  10. uses(RefreshDatabase::class);
  11. /**
  12. * library 三藏栏目的新路由:按译本浏览、作品详情、阅读入口与旧地址 301。
  13. *
  14. * 分类用真实的 public/data/category/default.json:id 3 = 长部 › 戒蕴品,
  15. * tag 为 sutta + dīghanikāya + sīlakkhandhavagga。
  16. */
  17. function libPaliText(int $book, int $paragraph, int $level, int $chapterLen, string $toc = ''): PaliText
  18. {
  19. $row = new PaliText;
  20. $row->forceFill([
  21. 'book' => $book,
  22. 'paragraph' => $paragraph,
  23. 'level' => $level,
  24. 'class' => '',
  25. 'toc' => $toc,
  26. 'text' => '',
  27. 'html' => '',
  28. 'pcd_book_id' => 0,
  29. 'chapter_len' => $chapterLen,
  30. 'chapter_strlen' => 100,
  31. 'uid' => (string) Str::uuid(),
  32. ])->save();
  33. return $row;
  34. }
  35. function libTagPaliText(PaliText $text, array $names): void
  36. {
  37. foreach ($names as $name) {
  38. $tag = Tag::where('name', $name)->first();
  39. if (! $tag) {
  40. $tag = new Tag;
  41. $tag->forceFill(['id' => (string) Str::uuid(), 'name' => $name, 'color' => 0, 'owner_id' => (string) Str::uuid()])->save();
  42. }
  43. (new TagMap)->forceFill([
  44. 'id' => (string) Str::uuid(), 'table_name' => 'pali_texts', 'anchor_id' => $text->uid, 'tag_id' => $tag->id,
  45. ])->save();
  46. }
  47. }
  48. function libProgress(string $channel, int $book, int $para, float $progress, ?string $title = null): ProgressChapter
  49. {
  50. $row = new ProgressChapter;
  51. $row->forceFill([
  52. 'id' => random_int(1, PHP_INT_MAX),
  53. 'uid' => (string) Str::uuid(),
  54. 'book' => $book,
  55. 'para' => $para,
  56. 'lang' => 'zh-Hans',
  57. 'channel_id' => $channel,
  58. 'progress' => $progress,
  59. 'title' => $title,
  60. 'all_trans' => 0,
  61. 'public' => 0,
  62. ])->save();
  63. return $row;
  64. }
  65. /**
  66. * 一部作品(sn 9100 = book 9100 的 3–102 段,两个章节 4、50),归入分类 3;
  67. * channel 收录了作品根节点(40%)和第二个章节。
  68. *
  69. * @return array{channel: string, sn: int}
  70. */
  71. function libTipitakaFixture(): array
  72. {
  73. $root = libPaliText(9100, 3, 1, 100, 'Sīlakkhandhavaggapāḷi');
  74. libPaliText(9100, 4, 2, 46, '1. Brahmajālasuttaṃ');
  75. libPaliText(9100, 50, 2, 53, '2. Sāmaññaphalasuttaṃ');
  76. libTagPaliText($root, ['sutta', 'dīghanikāya', 'sīlakkhandhavagga', 'pāḷi']);
  77. // book_titles.title 存的是丛书名,作品名取起点段落的 toc
  78. (new BookTitle)->forceFill(['sn' => 9100, 'book' => 9100, 'paragraph' => 3, 'title' => 'dīghanikāyapāḷi'])->save();
  79. $channel = makeChannel(makeStudio('lib-owner'), 'lib channel');
  80. libProgress($channel, 9100, 3, 0.4, '戒蕴品');
  81. libProgress($channel, 9100, 50, 0.8, '沙门果经');
  82. return ['channel' => $channel, 'sn' => 9100];
  83. }
  84. it('lists public channels with their work counts', function () {
  85. ['channel' => $channel] = libTipitakaFixture();
  86. $private = makeChannel(makeStudio('lib-private'), 'secret channel');
  87. Channel::where('uid', $private)->update(['status' => 10]);
  88. libProgress($private, 9100, 3, 0.9);
  89. $this->get('/library/tipitaka/channels')
  90. ->assertOk()
  91. ->assertSee('lib channel')
  92. ->assertSee("/library/tipitaka/channels/{$channel}", false)
  93. ->assertDontSee('secret channel');
  94. // 类型过滤:fixture 的 channel 是 translation
  95. $this->get('/library/tipitaka/channels?type=nissaya')->assertOk()->assertDontSee('lib channel');
  96. $this->get('/library/tipitaka/channels?type=translation')->assertOk()->assertSee('lib channel');
  97. // 语言过滤用 channel_lang;?lang= 是界面语言,不过滤列表
  98. $this->get('/library/tipitaka/channels?channel_lang=en')->assertOk()->assertDontSee('lib channel');
  99. $this->get('/library/tipitaka/channels?lang=en')->assertOk()->assertSee('lib channel');
  100. });
  101. it('lists the works of a channel with progress', function () {
  102. ['channel' => $channel] = libTipitakaFixture();
  103. $this->get("/library/tipitaka/channels/{$channel}")
  104. ->assertOk()
  105. ->assertSee('戒蕴品')
  106. ->assertSee("/library/tipitaka/channels/{$channel}/work/9100", false)
  107. ->assertSee('40%');
  108. });
  109. it('returns 404 for an unknown channel', function () {
  110. $this->get('/library/tipitaka/channels/'.Str::uuid())->assertNotFound();
  111. });
  112. it('shows the work under its category in the channel tree', function () {
  113. ['channel' => $channel] = libTipitakaFixture();
  114. $other = makeChannel(makeStudio('lib-other'), 'other channel');
  115. $this->get("/library/tipitaka/channels/{$channel}/category/1")
  116. ->assertOk()
  117. ->assertSee('戒蕴品')
  118. ->assertSee("/library/tipitaka/channels/{$channel}/work/9100", false);
  119. // 没收录的 channel 默认不列,?all=1 时置灰列出
  120. $this->get("/library/tipitaka/channels/{$other}/category/1")
  121. ->assertOk()
  122. ->assertDontSee('Sīlakkhandhavaggapāḷi');
  123. $this->get("/library/tipitaka/channels/{$other}/category/1?all=1")
  124. ->assertOk()
  125. ->assertSee('Sīlakkhandhavaggapāḷi')
  126. ->assertSee('is-missing');
  127. });
  128. it('shows every top category when no category is given', function () {
  129. ['channel' => $channel] = libTipitakaFixture();
  130. // 「全部」:四个藏都作为顶层,本 channel 只在经藏下有作品,所以只画出经藏这一棵
  131. $this->get("/library/tipitaka/channels/{$channel}/category")
  132. ->assertOk()
  133. ->assertSee('戒蕴品')
  134. ->assertSee("/library/tipitaka/channels/{$channel}/category/1", false);
  135. });
  136. it('returns 404 for an unknown category', function () {
  137. ['channel' => $channel] = libTipitakaFixture();
  138. $this->get("/library/tipitaka/channels/{$channel}/category/99999")->assertNotFound();
  139. $this->get('/library/tipitaka/category/99999')->assertNotFound();
  140. });
  141. it('shows the work detail with its chapters', function () {
  142. ['channel' => $channel, 'sn' => $sn] = libTipitakaFixture();
  143. $this->get("/library/tipitaka/channels/{$channel}/work/{$sn}")
  144. ->assertOk()
  145. ->assertSee('戒蕴品')
  146. ->assertSee("/library/tipitaka/channels/{$channel}/work/{$sn}/read", false)
  147. // 有译文的章节可点,没有的不出链接
  148. ->assertSee("/library/tipitaka/channels/{$channel}/chapter/9100-50", false)
  149. ->assertDontSee("/library/tipitaka/channels/{$channel}/chapter/9100-4\"", false);
  150. });
  151. it('returns 404 when the channel does not include the work', function () {
  152. ['sn' => $sn] = libTipitakaFixture();
  153. $other = makeChannel(makeStudio('lib-other'), 'other channel');
  154. $this->get("/library/tipitaka/channels/{$other}/work/{$sn}")->assertNotFound();
  155. $this->get("/library/tipitaka/channels/{$other}/work/424242")->assertNotFound();
  156. });
  157. it('redirects the read entry to the first translated chapter', function () {
  158. ['channel' => $channel, 'sn' => $sn] = libTipitakaFixture();
  159. $this->get("/library/tipitaka/channels/{$channel}/work/{$sn}/read")
  160. ->assertStatus(302)
  161. ->assertRedirect("/library/tipitaka/channels/{$channel}/chapter/9100-50");
  162. });
  163. it('falls back to the start of the work when no chapter is translated', function () {
  164. ['sn' => $sn] = libTipitakaFixture();
  165. $other = makeChannel(makeStudio('lib-other'), 'other channel');
  166. $this->get("/library/tipitaka/channels/{$other}/work/{$sn}/read")
  167. ->assertRedirect("/library/tipitaka/channels/{$other}/chapter/9100-3");
  168. });
  169. it('redirects the legacy reader url permanently and keeps the other query', function () {
  170. ['channel' => $channel] = libTipitakaFixture();
  171. $this->get("/library/tipitaka/9100-50/read?channel={$channel}&comm=abc")
  172. ->assertStatus(301)
  173. ->assertRedirect("/library/tipitaka/channels/{$channel}/chapter/9100-50?comm=abc");
  174. });
  175. it('redirects the legacy work url permanently', function () {
  176. ['channel' => $channel] = libTipitakaFixture();
  177. $uid = ProgressChapter::where('channel_id', $channel)->where('para', 3)->value('uid');
  178. $this->get("/library/tipitaka/{$uid}")
  179. ->assertStatus(301)
  180. ->assertRedirect("/library/tipitaka/channels/{$channel}/work/9100");
  181. $this->get('/library/tipitaka/'.Str::uuid())->assertNotFound();
  182. });
  183. it('lists the versions of a category and links them to the channel work page', function () {
  184. ['channel' => $channel] = libTipitakaFixture();
  185. $this->get('/library/tipitaka/category/3')
  186. ->assertOk()
  187. ->assertSee('lib channel')
  188. ->assertSee("/library/tipitaka/channels/{$channel}/work/9100", false);
  189. });
  190. it('links the works of a leaf category to the work versions page', function () {
  191. libTipitakaFixture();
  192. $this->get('/library/tipitaka/category/3')
  193. ->assertOk()
  194. ->assertSee('/library/tipitaka/work/9100', false)
  195. ->assertSee('Sīlakkhandhavaggapāḷi')
  196. ->assertDontSee('dīghanikāyapāḷi');
  197. // 旧的 ?book= 过滤 301 到作品版本页
  198. $this->get('/library/tipitaka/category/3?book=9100-3')
  199. ->assertStatus(301)
  200. ->assertRedirect('/library/tipitaka/work/9100');
  201. });
  202. it('lists every public version of a work grouped by type', function () {
  203. ['channel' => $channel] = libTipitakaFixture();
  204. $english = makeChannel(makeStudio('lib-en'), 'english channel');
  205. Channel::where('uid', $english)->update(['lang' => 'en']);
  206. libProgress($english, 9100, 3, 0.25);
  207. $this->get('/library/tipitaka/work/9100')
  208. ->assertOk()
  209. ->assertSee('lib channel')
  210. ->assertSee('english channel')
  211. ->assertSee("/library/tipitaka/channels/{$channel}/work/9100", false)
  212. ->assertSee('40%')
  213. ->assertSee('id="type-translation"', false);
  214. $this->get('/library/tipitaka/work/9100?channel_lang=en')
  215. ->assertOk()
  216. ->assertSee('english channel')
  217. ->assertDontSee('lib channel');
  218. $this->get('/library/tipitaka/work/424242')->assertNotFound();
  219. });
  220. it('redirects the legacy channel query to the channel pages', function () {
  221. ['channel' => $channel] = libTipitakaFixture();
  222. $this->get("/library/tipitaka?channel={$channel}")
  223. ->assertStatus(301)
  224. ->assertRedirect("/library/tipitaka/channels/{$channel}");
  225. $this->get("/library/tipitaka/category/3?channel={$channel}")
  226. ->assertStatus(301)
  227. ->assertRedirect("/library/tipitaka/channels/{$channel}/category/3");
  228. });
  229. it('renders the tipitaka home page', function () {
  230. libTipitakaFixture();
  231. $this->get('/library/tipitaka')
  232. ->assertOk()
  233. ->assertSee('lib channel')
  234. ->assertSee('/library/tipitaka/category/1', false);
  235. });