Compare commits
	
		
			4 Commits
		
	
	
		
			logger-ins
			...
			render-ap
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | d8434a206f | ||
|   | 83159600ea | ||
|   | 12b82aca5f | ||
|   | 73842166ee | 
| @@ -16,6 +16,7 @@ | |||||||
|  |  | ||||||
| ### Client | ### Client | ||||||
| - Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように | - Enhance: ハッシュタグ入力時に、本文の末尾の行に何も書かれていない場合は新たにスペースを追加しないように | ||||||
|  | - Enhance: ActivityPubをサポートしているウェブリンクを展開できるように | ||||||
|  |  | ||||||
| ## 2023.12.2 | ## 2023.12.2 | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								locales/index.d.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								locales/index.d.ts
									
									
									
									
										vendored
									
									
								
							| @@ -609,6 +609,7 @@ export interface Locale { | |||||||
|     "enablePlayer": string; |     "enablePlayer": string; | ||||||
|     "disablePlayer": string; |     "disablePlayer": string; | ||||||
|     "expandTweet": string; |     "expandTweet": string; | ||||||
|  |     "expandNote": string; | ||||||
|     "themeEditor": string; |     "themeEditor": string; | ||||||
|     "description": string; |     "description": string; | ||||||
|     "describeFile": string; |     "describeFile": string; | ||||||
|   | |||||||
| @@ -606,6 +606,7 @@ useCw: "内容を隠す" | |||||||
| enablePlayer: "プレイヤーを開く" | enablePlayer: "プレイヤーを開く" | ||||||
| disablePlayer: "プレイヤーを閉じる" | disablePlayer: "プレイヤーを閉じる" | ||||||
| expandTweet: "ポストを展開する" | expandTweet: "ポストを展開する" | ||||||
|  | expandNote: "ノートを展開する" | ||||||
| themeEditor: "テーマエディター" | themeEditor: "テーマエディター" | ||||||
| description: "説明" | description: "説明" | ||||||
| describeFile: "キャプションを付ける" | describeFile: "キャプションを付ける" | ||||||
|   | |||||||
| @@ -83,7 +83,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 					</div> | 					</div> | ||||||
| 					<MkPoll v-if="appearNote.poll" :note="appearNote" :class="$style.poll"/> | 					<MkPoll v-if="appearNote.poll" :note="appearNote" :class="$style.poll"/> | ||||||
| 					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/> | 					<MkUrlPreview v-for="url in urls" :key="url" :url="url" :compact="true" :detail="false" :class="$style.urlPreview"/> | ||||||
| 					<div v-if="appearNote.renote" :class="$style.quote"><MkNoteSimple :note="appearNote.renote" :class="$style.quoteNote"/></div> | 					<MkNoteSimple v-if="appearNote.renote" :class="$style.quote" :note="appearNote.renote" :quoted="true"/> | ||||||
| 					<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false"> | 					<button v-if="isLong && collapsed" :class="$style.collapsed" class="_button" @click="collapsed = false"> | ||||||
| 						<span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span> | 						<span :class="$style.collapsedLabel">{{ i18n.ts.showMore }}</span> | ||||||
| 					</button> | 					</button> | ||||||
| @@ -801,14 +801,7 @@ function emitUpdReaction(emoji: string, delta: number) { | |||||||
| } | } | ||||||
|  |  | ||||||
| .quote { | .quote { | ||||||
| 	padding: 8px 0; | 	margin: 8px 0; | ||||||
| } |  | ||||||
|  |  | ||||||
| .quoteNote { |  | ||||||
| 	padding: 16px; |  | ||||||
| 	border: dashed 1px var(--renote); |  | ||||||
| 	border-radius: 8px; |  | ||||||
| 	overflow: clip; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| .channel { | .channel { | ||||||
| @@ -947,12 +940,6 @@ function emitUpdReaction(emoji: string, delta: number) { | |||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @container (max-width: 250px) { |  | ||||||
| 	.quoteNote { |  | ||||||
| 		padding: 12px; |  | ||||||
| 	} |  | ||||||
| } |  | ||||||
|  |  | ||||||
| .muted { | .muted { | ||||||
| 	padding: 8px; | 	padding: 8px; | ||||||
| 	text-align: center; | 	text-align: center; | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| --> | --> | ||||||
|  |  | ||||||
| <template> | <template> | ||||||
| <div :class="$style.root"> | <div :class="[$style.root, quoted ? $style.quoted : null]"> | ||||||
| 	<MkAvatar :class="$style.avatar" :user="note.user" link preview/> | 	<MkAvatar :class="$style.avatar" :user="note.user" link preview/> | ||||||
| 	<div :class="$style.main"> | 	<div :class="$style.main"> | ||||||
| 		<MkNoteHeader :class="$style.header" :note="note" :mini="true"/> | 		<MkNoteHeader :class="$style.header" :note="note" :mini="true"/> | ||||||
| @@ -30,6 +30,8 @@ import MkCwButton from '@/components/MkCwButton.vue'; | |||||||
|  |  | ||||||
| const props = defineProps<{ | const props = defineProps<{ | ||||||
| 	note: Misskey.entities.Note; | 	note: Misskey.entities.Note; | ||||||
|  | 	pinned?: boolean; | ||||||
|  | 	quoted?: boolean; | ||||||
| }>(); | }>(); | ||||||
|  |  | ||||||
| const showContent = ref(false); | const showContent = ref(false); | ||||||
| @@ -78,12 +80,23 @@ const showContent = ref(false); | |||||||
| 	padding: 0; | 	padding: 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .quoted { | ||||||
|  | 	padding: 16px; | ||||||
|  | 	border: dashed 1px var(--renote); | ||||||
|  | 	border-radius: 8px; | ||||||
|  | 	overflow: clip; | ||||||
|  | } | ||||||
|  |  | ||||||
| @container (min-width: 250px) { | @container (min-width: 250px) { | ||||||
| 	.avatar { | 	.avatar { | ||||||
| 		margin: 0 10px 0 0; | 		margin: 0 10px 0 0; | ||||||
| 		width: 40px; | 		width: 40px; | ||||||
| 		height: 40px; | 		height: 40px; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	.quoted { | ||||||
|  | 		padding: 12px; | ||||||
|  | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| @container (min-width: 350px) { | @container (min-width: 350px) { | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| --> | --> | ||||||
|  |  | ||||||
| <template> | <template> | ||||||
| <template v-if="player.url && playerEnabled"> | <div v-if="player.url && playerEnabled"> | ||||||
| 	<div | 	<div | ||||||
| 		:class="$style.player" | 		:class="$style.player" | ||||||
| 		:style="player.width ? `padding: ${(player.height || 0) / player.width * 100}% 0 0` : `padding: ${(player.height || 0)}px 0 0`" | 		:style="player.width ? `padding: ${(player.height || 0) / player.width * 100}% 0 0` : `padding: ${(player.height || 0)}px 0 0`" | ||||||
| @@ -25,9 +25,9 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 			<i class="ti ti-x"></i> {{ i18n.ts.disablePlayer }} | 			<i class="ti ti-x"></i> {{ i18n.ts.disablePlayer }} | ||||||
| 		</MkButton> | 		</MkButton> | ||||||
| 	</div> | 	</div> | ||||||
| </template> | </div> | ||||||
| <template v-else-if="tweetId && tweetExpanded"> | <div v-else-if="postExpanded"> | ||||||
| 	<div ref="twitter"> | 	<div v-if="tweetId" ref="twitter"> | ||||||
| 		<iframe | 		<iframe | ||||||
| 			ref="tweet" | 			ref="tweet" | ||||||
| 			allow="fullscreen;web-share" | 			allow="fullscreen;web-share" | ||||||
| @@ -37,12 +37,13 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 			:src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${defaultStore.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`" | 			:src="`https://platform.twitter.com/embed/index.html?embedId=${embedId}&hideCard=false&hideThread=false&lang=en&theme=${defaultStore.state.darkMode ? 'dark' : 'light'}&id=${tweetId}`" | ||||||
| 		></iframe> | 		></iframe> | ||||||
| 	</div> | 	</div> | ||||||
|  | 	<MkNoteSimple v-else-if="note" :note="note" :quoted="true"/> | ||||||
| 	<div :class="$style.action"> | 	<div :class="$style.action"> | ||||||
| 		<MkButton :small="true" inline @click="tweetExpanded = false"> | 		<MkButton :small="true" inline @click="postExpanded = false"> | ||||||
| 			<i class="ti ti-x"></i> {{ i18n.ts.close }} | 			<i v-if="tweetId" class="ti ti-x"></i> {{ i18n.ts.close }} | ||||||
| 		</MkButton> | 		</MkButton> | ||||||
| 	</div> | 	</div> | ||||||
| </template> | </div> | ||||||
| <div v-else> | <div v-else> | ||||||
| 	<component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substring(local.length) : url" rel="nofollow noopener" :target="target" :title="url"> | 	<component :is="self ? 'MkA' : 'a'" :class="[$style.link, { [$style.compact]: compact }]" :[attr]="self ? url.substring(local.length) : url" rel="nofollow noopener" :target="target" :title="url"> | ||||||
| 		<div v-if="thumbnail && !sensitive" :class="$style.thumbnail" :style="defaultStore.state.dataSaver.urlPreview ? '' : `background-image: url('${thumbnail}')`"> | 		<div v-if="thumbnail && !sensitive" :class="$style.thumbnail" :style="defaultStore.state.dataSaver.urlPreview ? '' : `background-image: url('${thumbnail}')`"> | ||||||
| @@ -66,10 +67,15 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| 	</component> | 	</component> | ||||||
| 	<template v-if="showActions"> | 	<template v-if="showActions"> | ||||||
| 		<div v-if="tweetId" :class="$style.action"> | 		<div v-if="tweetId" :class="$style.action"> | ||||||
| 			<MkButton :small="true" inline @click="tweetExpanded = true"> | 			<MkButton :small="true" inline @click="postExpanded = true"> | ||||||
| 				<i class="ti ti-brand-x"></i> {{ i18n.ts.expandTweet }} | 				<i class="ti ti-brand-x"></i> {{ i18n.ts.expandTweet }} | ||||||
| 			</MkButton> | 			</MkButton> | ||||||
| 		</div> | 		</div> | ||||||
|  | 		<div v-if="noteUrl || note" :class="$style.action"> | ||||||
|  | 			<MkButton :small="true" inline @click="resolveNote()"> | ||||||
|  | 				{{ i18n.ts.expandNote }} | ||||||
|  | 			</MkButton> | ||||||
|  | 		</div> | ||||||
| 		<div v-if="!playerEnabled && player.url" :class="$style.action"> | 		<div v-if="!playerEnabled && player.url" :class="$style.action"> | ||||||
| 			<MkButton :small="true" inline @click="playerEnabled = true"> | 			<MkButton :small="true" inline @click="playerEnabled = true"> | ||||||
| 				<i class="ti ti-player-play"></i> {{ i18n.ts.enablePlayer }} | 				<i class="ti ti-player-play"></i> {{ i18n.ts.enablePlayer }} | ||||||
| @@ -85,11 +91,13 @@ SPDX-License-Identifier: AGPL-3.0-only | |||||||
| <script lang="ts" setup> | <script lang="ts" setup> | ||||||
| import { defineAsyncComponent, onUnmounted, ref } from 'vue'; | import { defineAsyncComponent, onUnmounted, ref } from 'vue'; | ||||||
| import type { summaly } from 'summaly'; | import type { summaly } from 'summaly'; | ||||||
|  | import type * as Misskey from 'misskey-js'; | ||||||
| import { url as local } from '@/config.js'; | import { url as local } from '@/config.js'; | ||||||
| import { i18n } from '@/i18n.js'; | import { i18n } from '@/i18n.js'; | ||||||
| import * as os from '@/os.js'; | import * as os from '@/os.js'; | ||||||
| import { deviceKind } from '@/scripts/device-kind.js'; | import { deviceKind } from '@/scripts/device-kind.js'; | ||||||
| import MkButton from '@/components/MkButton.vue'; | import MkButton from '@/components/MkButton.vue'; | ||||||
|  | import MkNoteSimple from '@/components/MkNoteSimple.vue'; | ||||||
| import { versatileLang } from '@/scripts/intl-const.js'; | import { versatileLang } from '@/scripts/intl-const.js'; | ||||||
| import { defaultStore } from '@/store.js'; | import { defaultStore } from '@/store.js'; | ||||||
|  |  | ||||||
| @@ -126,7 +134,9 @@ const player = ref({ | |||||||
| } as SummalyResult['player']); | } as SummalyResult['player']); | ||||||
| const playerEnabled = ref(false); | const playerEnabled = ref(false); | ||||||
| const tweetId = ref<string | null>(null); | const tweetId = ref<string | null>(null); | ||||||
| const tweetExpanded = ref(props.detail); | const noteUrl = ref<string | null>(null); | ||||||
|  | const note = ref<Misskey.entities.Note | null>(null); | ||||||
|  | const postExpanded = ref(props.detail); | ||||||
| const embedId = `embed${Math.random().toString().replace(/\D/, '')}`; | const embedId = `embed${Math.random().toString().replace(/\D/, '')}`; | ||||||
| const tweetHeight = ref(150); | const tweetHeight = ref(150); | ||||||
| const unknownUrl = ref(false); | const unknownUrl = ref(false); | ||||||
| @@ -172,9 +182,40 @@ window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLa | |||||||
| 		sitename.value = info.sitename; | 		sitename.value = info.sitename; | ||||||
| 		player.value = info.player; | 		player.value = info.player; | ||||||
| 		sensitive.value = info.sensitive ?? false; | 		sensitive.value = info.sensitive ?? false; | ||||||
|  | 		noteUrl.value = info.activityPub; | ||||||
|  | 		if (postExpanded.value) { | ||||||
|  | 			resolveNote(); | ||||||
|  | 		} | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| function adjustTweetHeight(message: any) { | async function resolveNote(): Promise<void> { | ||||||
|  | 	if (note.value) { | ||||||
|  | 		// Reuse the data | ||||||
|  | 		postExpanded.value = true; | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	if (!noteUrl.value) { | ||||||
|  | 		// Note does not exist | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	try { | ||||||
|  | 		fetching.value = true; | ||||||
|  | 		const result = await os.api('ap/show', { uri: noteUrl.value }); | ||||||
|  | 		if (result.type === 'Note') { | ||||||
|  | 			note.value = result.object; | ||||||
|  | 			postExpanded.value = true; | ||||||
|  | 		} else { | ||||||
|  | 			postExpanded.value = false; | ||||||
|  | 		} | ||||||
|  | 	} finally { | ||||||
|  | 		// Prevent repeated resolving | ||||||
|  | 		noteUrl.value = null; | ||||||
|  | 		fetching.value = false; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | function adjustTweetHeight(message: any): void { | ||||||
| 	if (message.origin !== 'https://platform.twitter.com') return; | 	if (message.origin !== 'https://platform.twitter.com') return; | ||||||
| 	const embed = message.data?.['twttr.embed']; | 	const embed = message.data?.['twttr.embed']; | ||||||
| 	if (embed?.method !== 'twttr.private.resize') return; | 	if (embed?.method !== 'twttr.private.resize') return; | ||||||
|   | |||||||
| @@ -3,10 +3,11 @@ | |||||||
|  * SPDX-License-Identifier: AGPL-3.0-only |  * SPDX-License-Identifier: AGPL-3.0-only | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| import { describe, test, assert, afterEach } from 'vitest'; | import { describe, test, assert, afterEach, beforeAll, vi } from 'vitest'; | ||||||
| import { render, cleanup, type RenderResult } from '@testing-library/vue'; | import { render, cleanup, type RenderResult } from '@testing-library/vue'; | ||||||
| import './init'; | import './init'; | ||||||
| import type { summaly } from 'summaly'; | import type { summaly } from 'summaly'; | ||||||
|  | import type * as misskey from 'misskey-js'; | ||||||
| import { components } from '@/components/index.js'; | import { components } from '@/components/index.js'; | ||||||
| import { directives } from '@/directives/index.js'; | import { directives } from '@/directives/index.js'; | ||||||
| import MkUrlPreview from '@/components/MkUrlPreview.vue'; | import MkUrlPreview from '@/components/MkUrlPreview.vue'; | ||||||
| @@ -47,13 +48,18 @@ describe('MkUrlPreview', () => { | |||||||
| 		return result; | 		return result; | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	const renderAndOpenPreview = async (summary: Partial<SummalyResult>): Promise<HTMLIFrameElement | null> => { | 	const renderAndOpenPreview = async (summary: Partial<SummalyResult>): Promise<RenderResult> => { | ||||||
| 		const mkUrlPreview = await renderPreviewBy(summary); | 		const mkUrlPreview = await renderPreviewBy(summary); | ||||||
| 		const buttons = mkUrlPreview.getAllByRole('button'); | 		const buttons = mkUrlPreview.getAllByRole('button'); | ||||||
| 		buttons[0].click(); | 		buttons[0].click(); | ||||||
| 		// Wait for the click event to be fired | 		// Wait for the click event to be fired | ||||||
| 		await Promise.resolve(); | 		await Promise.resolve(); | ||||||
|  |  | ||||||
|  | 		return mkUrlPreview; | ||||||
|  | 	}; | ||||||
|  |  | ||||||
|  | 	const renderAndOpenPreviewInIFrame = async (summary: Partial<SummalyResult>): Promise<HTMLIFrameElement | null> => { | ||||||
|  | 		const mkUrlPreview = await renderAndOpenPreview(summary); | ||||||
| 		return mkUrlPreview.container.querySelector('iframe'); | 		return mkUrlPreview.container.querySelector('iframe'); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| @@ -85,7 +91,7 @@ describe('MkUrlPreview', () => { | |||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	test('Having a player should setup the iframe', async () => { | 	test('Having a player should setup the iframe', async () => { | ||||||
| 		const iframe = await renderAndOpenPreview({ | 		const iframe = await renderAndOpenPreviewInIFrame({ | ||||||
| 			url: 'https://example.local', | 			url: 'https://example.local', | ||||||
| 			player: { | 			player: { | ||||||
| 				url: 'https://example.local/player', | 				url: 'https://example.local/player', | ||||||
| @@ -103,7 +109,7 @@ describe('MkUrlPreview', () => { | |||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	test('Having a player with `allow` field should set permissions', async () => { | 	test('Having a player with `allow` field should set permissions', async () => { | ||||||
| 		const iframe = await renderAndOpenPreview({ | 		const iframe = await renderAndOpenPreviewInIFrame({ | ||||||
| 			url: 'https://example.local', | 			url: 'https://example.local', | ||||||
| 			player: { | 			player: { | ||||||
| 				url: 'https://example.local/player', | 				url: 'https://example.local/player', | ||||||
| @@ -117,7 +123,7 @@ describe('MkUrlPreview', () => { | |||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	test('Having a player width should keep the fixed aspect ratio', async () => { | 	test('Having a player width should keep the fixed aspect ratio', async () => { | ||||||
| 		const iframe = await renderAndOpenPreview({ | 		const iframe = await renderAndOpenPreviewInIFrame({ | ||||||
| 			url: 'https://example.local', | 			url: 'https://example.local', | ||||||
| 			player: { | 			player: { | ||||||
| 				url: 'https://example.local/player', | 				url: 'https://example.local/player', | ||||||
| @@ -131,7 +137,7 @@ describe('MkUrlPreview', () => { | |||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	test('Having a player width should keep the fixed height', async () => { | 	test('Having a player width should keep the fixed height', async () => { | ||||||
| 		const iframe = await renderAndOpenPreview({ | 		const iframe = await renderAndOpenPreviewInIFrame({ | ||||||
| 			url: 'https://example.local', | 			url: 'https://example.local', | ||||||
| 			player: { | 			player: { | ||||||
| 				url: 'https://example.local/player', | 				url: 'https://example.local/player', | ||||||
| @@ -145,7 +151,7 @@ describe('MkUrlPreview', () => { | |||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	test('Loading a tweet in iframe', async () => { | 	test('Loading a tweet in iframe', async () => { | ||||||
| 		const iframe = await renderAndOpenPreview({ | 		const iframe = await renderAndOpenPreviewInIFrame({ | ||||||
| 			url: 'https://twitter.com/i/web/status/1685072521782325249', | 			url: 'https://twitter.com/i/web/status/1685072521782325249', | ||||||
| 		}); | 		}); | ||||||
| 		assert.exists(iframe, 'iframe should exist'); | 		assert.exists(iframe, 'iframe should exist'); | ||||||
| @@ -154,11 +160,48 @@ describe('MkUrlPreview', () => { | |||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	test('Loading a post in iframe', async () => { | 	test('Loading a post in iframe', async () => { | ||||||
| 		const iframe = await renderAndOpenPreview({ | 		const iframe = await renderAndOpenPreviewInIFrame({ | ||||||
| 			url: 'https://x.com/i/web/status/1685072521782325249', | 			url: 'https://x.com/i/web/status/1685072521782325249', | ||||||
| 		}); | 		}); | ||||||
| 		assert.exists(iframe, 'iframe should exist'); | 		assert.exists(iframe, 'iframe should exist'); | ||||||
| 		assert.strictEqual(iframe?.getAttribute('allow'), 'fullscreen;web-share'); | 		assert.strictEqual(iframe?.getAttribute('allow'), 'fullscreen;web-share'); | ||||||
| 		assert.strictEqual(iframe?.getAttribute('sandbox'), 'allow-popups allow-popups-to-escape-sandbox allow-scripts allow-same-origin'); | 		assert.strictEqual(iframe?.getAttribute('sandbox'), 'allow-popups allow-popups-to-escape-sandbox allow-scripts allow-same-origin'); | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
|  | 	describe('ActivityPub notes', () => { | ||||||
|  | 		afterEach(() => { | ||||||
|  | 			vi.clearAllMocks(); | ||||||
|  | 		}); | ||||||
|  |  | ||||||
|  | 		test('Preview a note', async () => { | ||||||
|  | 			vi.mock('@/os', () => { | ||||||
|  | 				return { | ||||||
|  | 					api(endpoint: string): unknown { | ||||||
|  | 						if (endpoint === 'ap/show') { | ||||||
|  | 							return { | ||||||
|  | 								type: 'Note', | ||||||
|  | 								object: { | ||||||
|  | 									text: 'Mizuki', | ||||||
|  | 									createdAt: new Date().toISOString(), | ||||||
|  | 									user: {}, | ||||||
|  | 									files: [] as misskey.entities.DriveFile[], | ||||||
|  | 								} as misskey.entities.Note, | ||||||
|  | 							}; | ||||||
|  | 						} | ||||||
|  | 						throw new Error(`Unexpected api call ${endpoint}`); | ||||||
|  | 					}, | ||||||
|  | 				}; | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			const url = 'https://example.local'; | ||||||
|  | 			const renderResult = await renderAndOpenPreview({ | ||||||
|  | 				url, | ||||||
|  | 				description: 'Misskey', | ||||||
|  | 				activityPub: url, | ||||||
|  | 			}); | ||||||
|  |  | ||||||
|  | 			assert.notExists(renderResult.queryByText('Misskey'), 'Original description should disappear'); | ||||||
|  | 			assert.exists(renderResult.queryByText('Mizuki'), 'ActivityPub fetch result should appear'); | ||||||
|  | 		}); | ||||||
|  | 	}); | ||||||
| }); | }); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user