Merge branch 'develop' into fix-7311
This commit is contained in:
commit
0db9ec2d9b
|
@ -50,6 +50,9 @@
|
|||
- Fix: エンドポイント`notes/translate`のエラーを改善
|
||||
- Fix: CleanRemoteFilesProcessorService report progress from 100% (#13632)
|
||||
- Fix: 一部の音声ファイルが映像ファイルとして扱われる問題を修正
|
||||
- Fix: リプライのみの引用リノートと、CWのみの引用リノートが純粋なリノートとして誤って扱われてしまう問題を修正
|
||||
- Fix: 登録にメール認証が必須になっている場合、登録されているメールアドレスを削除できないように
|
||||
(Cherry-picked from https://github.com/MisskeyIO/misskey/pull/606)
|
||||
|
||||
## 2024.3.1
|
||||
|
||||
|
|
|
@ -30,9 +30,13 @@ Cypress.Commands.add('visitHome', () => {
|
|||
})
|
||||
|
||||
Cypress.Commands.add('resetState', () => {
|
||||
// iframe.contentWindow.indexedDB.deleteDatabase() がchromeのバグで使用できないため、indexedDBを無効化している。
|
||||
// see https://github.com/misskey-dev/misskey/issues/13605#issuecomment-2053652123
|
||||
/*
|
||||
cy.window().then(win => {
|
||||
win.indexedDB.deleteDatabase('keyval-store');
|
||||
});
|
||||
*/
|
||||
cy.request('POST', '/api/reset-db', {}).as('reset');
|
||||
cy.get('@reset').its('status').should('equal', 204);
|
||||
cy.reload(true);
|
||||
|
|
|
@ -13,7 +13,7 @@ import type { NotesRepository } from '@/models/_.js';
|
|||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { FanoutTimelineName, FanoutTimelineService } from '@/core/FanoutTimelineService.js';
|
||||
import { isUserRelated } from '@/misc/is-user-related.js';
|
||||
import { isPureRenote } from '@/misc/is-pure-renote.js';
|
||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import { isReply } from '@/misc/is-reply.js';
|
||||
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
|
||||
|
@ -95,7 +95,7 @@ export class FanoutTimelineEndpointService {
|
|||
|
||||
if (ps.excludePureRenotes) {
|
||||
const parentFilter = filter;
|
||||
filter = (note) => !isPureRenote(note) && parentFilter(note);
|
||||
filter = (note) => (!isRenote(note) || isQuote(note)) && parentFilter(note);
|
||||
}
|
||||
|
||||
if (ps.me) {
|
||||
|
@ -116,7 +116,7 @@ export class FanoutTimelineEndpointService {
|
|||
filter = (note) => {
|
||||
if (isUserRelated(note, userIdsWhoBlockingMe, ps.ignoreAuthorFromBlock)) return false;
|
||||
if (isUserRelated(note, userIdsWhoMeMuting, ps.ignoreAuthorFromMute)) return false;
|
||||
if (isPureRenote(note) && isUserRelated(note, userIdsWhoMeMutingRenotes, ps.ignoreAuthorFromMute)) return false;
|
||||
if (isRenote(note) && !isQuote(note) && isUserRelated(note, userIdsWhoMeMutingRenotes, ps.ignoreAuthorFromMute)) return false;
|
||||
if (isInstanceMuted(note, userMutedInstances)) return false;
|
||||
|
||||
return parentFilter(note);
|
||||
|
|
|
@ -306,7 +306,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}
|
||||
|
||||
// Check blocking
|
||||
if (data.renote && !this.isQuote(data)) {
|
||||
if (this.isRenote(data) && !this.isQuote(data)) {
|
||||
if (data.renote.userHost === null) {
|
||||
if (data.renote.userId !== user.id) {
|
||||
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
|
||||
|
@ -641,7 +641,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}
|
||||
|
||||
// If it is renote
|
||||
if (data.renote) {
|
||||
if (this.isRenote(data)) {
|
||||
const type = this.isQuote(data) ? 'quote' : 'renote';
|
||||
|
||||
// Notify
|
||||
|
@ -725,9 +725,20 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private isQuote(note: Option): note is Option & { renote: MiNote } {
|
||||
// sync with misc/is-quote.ts
|
||||
return !!note.renote && (!!note.text || !!note.cw || (!!note.files && !!note.files.length) || !!note.poll);
|
||||
private isRenote(note: Option): note is Option & { renote: MiNote } {
|
||||
return note.renote != null;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private isQuote(note: Option & { renote: MiNote }): note is Option & { renote: MiNote } & (
|
||||
{ text: string } | { cw: string } | { reply: MiNote } | { poll: IPoll } | { files: MiDriveFile[] }
|
||||
) {
|
||||
// NOTE: SYNC WITH misc/is-quote.ts
|
||||
return note.text != null ||
|
||||
note.reply != null ||
|
||||
note.cw != null ||
|
||||
note.poll != null ||
|
||||
(note.files != null && note.files.length > 0);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -795,7 +806,7 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
private async renderNoteOrRenoteActivity(data: Option, note: MiNote) {
|
||||
if (data.localOnly) return null;
|
||||
|
||||
const content = data.renote && !this.isQuote(data)
|
||||
const content = this.isRenote(data) && !this.isQuote(data)
|
||||
? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note)
|
||||
: this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note);
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ import { bindThis } from '@/decorators.js';
|
|||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { SearchService } from '@/core/SearchService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { isPureRenote } from '@/misc/is-pure-renote.js';
|
||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
|
||||
@Injectable()
|
||||
export class NoteDeleteService {
|
||||
|
@ -79,7 +79,7 @@ export class NoteDeleteService {
|
|||
let renote: MiNote | null = null;
|
||||
|
||||
// if deleted note is renote
|
||||
if (isPureRenote(note)) {
|
||||
if (isRenote(note) && !isQuote(note)) {
|
||||
renote = await this.notesRepository.findOneBy({
|
||||
id: note.renoteId,
|
||||
});
|
||||
|
|
|
@ -1,15 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
|
||||
export function isPureRenote(note: MiNote): note is MiNote & { renoteId: NonNullable<MiNote['renoteId']> } {
|
||||
if (!note.renoteId) return false;
|
||||
|
||||
if (note.text) return false; // it's quoted with text
|
||||
if (note.fileIds.length !== 0) return false; // it's quoted with files
|
||||
if (note.hasPoll) return false; // it's quoted with poll
|
||||
return true;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
|
||||
// eslint-disable-next-line import/no-default-export
|
||||
export default function(note: MiNote): boolean {
|
||||
// sync with NoteCreateService.isQuote
|
||||
return note.renoteId != null && (note.text != null || note.hasPoll || (note.fileIds != null && note.fileIds.length > 0));
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { MiNote } from '@/models/Note.js';
|
||||
|
||||
type Renote =
|
||||
MiNote & {
|
||||
renoteId: NonNullable<MiNote['renoteId']>
|
||||
};
|
||||
|
||||
type Quote =
|
||||
Renote & ({
|
||||
text: NonNullable<MiNote['text']>
|
||||
} | {
|
||||
cw: NonNullable<MiNote['cw']>
|
||||
} | {
|
||||
replyId: NonNullable<MiNote['replyId']>
|
||||
reply: NonNullable<MiNote['reply']>
|
||||
} | {
|
||||
hasPoll: true
|
||||
});
|
||||
|
||||
export function isRenote(note: MiNote): note is Renote {
|
||||
return note.renoteId != null;
|
||||
}
|
||||
|
||||
export function isQuote(note: Renote): note is Quote {
|
||||
// NOTE: SYNC WITH NoteCreateService.isQuote
|
||||
return note.text != null ||
|
||||
note.cw != null ||
|
||||
note.replyId != null ||
|
||||
note.hasPoll ||
|
||||
note.fileIds.length > 0;
|
||||
}
|
|
@ -28,7 +28,7 @@ import { UtilityService } from '@/core/UtilityService.js';
|
|||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { IActivity } from '@/core/activitypub/type.js';
|
||||
import { isPureRenote } from '@/misc/is-pure-renote.js';
|
||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
|
||||
import type { FindOptionsWhere } from 'typeorm';
|
||||
|
||||
|
@ -91,7 +91,7 @@ export class ActivityPubServerService {
|
|||
*/
|
||||
@bindThis
|
||||
private async packActivity(note: MiNote): Promise<any> {
|
||||
if (isPureRenote(note)) {
|
||||
if (isRenote(note) && !isQuote(note)) {
|
||||
const renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId });
|
||||
return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note);
|
||||
}
|
||||
|
|
|
@ -194,6 +194,7 @@ export class FileServerService {
|
|||
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
|
||||
reply.header('Accept-Ranges', 'bytes');
|
||||
reply.header('Content-Length', chunksize);
|
||||
reply.code(206);
|
||||
} else {
|
||||
image = {
|
||||
data: fs.createReadStream(file.path),
|
||||
|
@ -263,7 +264,6 @@ export class FileServerService {
|
|||
const parts = range.replace(/bytes=/, '').split('-');
|
||||
const start = parseInt(parts[0], 10);
|
||||
let end = parts[1] ? parseInt(parts[1], 10) : file.file.size - 1;
|
||||
console.log(end);
|
||||
if (end > file.file.size) {
|
||||
end = file.file.size - 1;
|
||||
}
|
||||
|
@ -433,6 +433,7 @@ export class FileServerService {
|
|||
reply.header('Content-Range', `bytes ${start}-${end}/${file.file.size}`);
|
||||
reply.header('Accept-Ranges', 'bytes');
|
||||
reply.header('Content-Length', chunksize);
|
||||
reply.code(206);
|
||||
} else {
|
||||
image = {
|
||||
data: fs.createReadStream(file.path),
|
||||
|
@ -529,6 +530,9 @@ export class FileServerService {
|
|||
if (!file.storedInternal) {
|
||||
if (!(file.isLink && file.uri)) return '204';
|
||||
const result = await this.downloadAndDetectTypeFromUrl(file.uri);
|
||||
if (!file.size) {
|
||||
file.size = (await fs.promises.stat(result.path)).size;
|
||||
}
|
||||
return {
|
||||
...result,
|
||||
url: file.uri,
|
||||
|
|
|
@ -15,6 +15,7 @@ import { DI } from '@/di-symbols.js';
|
|||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { UserAuthService } from '@/core/UserAuthService.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
@ -39,6 +40,12 @@ export const meta = {
|
|||
code: 'UNAVAILABLE',
|
||||
id: 'a2defefb-f220-8849-0af6-17f816099323',
|
||||
},
|
||||
|
||||
emailRequired: {
|
||||
message: 'Email address is required.',
|
||||
code: 'EMAIL_REQUIRED',
|
||||
id: '324c7a88-59f2-492f-903f-89134f93e47e',
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
|
@ -66,6 +73,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
@Inject(DI.userProfilesRepository)
|
||||
private userProfilesRepository: UserProfilesRepository,
|
||||
|
||||
private metaService: MetaService,
|
||||
private userEntityService: UserEntityService,
|
||||
private emailService: EmailService,
|
||||
private userAuthService: UserAuthService,
|
||||
|
@ -97,6 +105,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (!res.available) {
|
||||
throw new ApiError(meta.errors.unavailable);
|
||||
}
|
||||
} else if ((await this.metaService.fetch()).emailRequiredForSignup) {
|
||||
throw new ApiError(meta.errors.emailRequired);
|
||||
}
|
||||
|
||||
await this.userProfilesRepository.update(me.id, {
|
||||
|
|
|
@ -16,7 +16,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
|||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { isPureRenote } from '@/misc/is-pure-renote.js';
|
||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
|
@ -275,7 +275,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
if (renote == null) {
|
||||
throw new ApiError(meta.errors.noSuchRenoteTarget);
|
||||
} else if (isPureRenote(renote)) {
|
||||
} else if (isRenote(renote) && !isQuote(renote)) {
|
||||
throw new ApiError(meta.errors.cannotReRenote);
|
||||
}
|
||||
|
||||
|
@ -321,7 +321,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
if (reply == null) {
|
||||
throw new ApiError(meta.errors.noSuchReplyTarget);
|
||||
} else if (isPureRenote(reply)) {
|
||||
} else if (isRenote(reply) && !isQuote(reply)) {
|
||||
throw new ApiError(meta.errors.cannotReplyToPureRenote);
|
||||
} else if (!await this.noteEntityService.isVisibleForMe(reply, me.id)) {
|
||||
throw new ApiError(meta.errors.cannotReplyToInvisibleNote);
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Test } from '@nestjs/testing';
|
||||
|
||||
import { CoreModule } from '@/core/CoreModule.js';
|
||||
import { NoteCreateService } from '@/core/NoteCreateService.js';
|
||||
import { GlobalModule } from '@/GlobalModule.js';
|
||||
import { MiNote } from '@/models/Note.js';
|
||||
import { IPoll } from '@/models/Poll.js';
|
||||
import { MiDriveFile } from '@/models/DriveFile.js';
|
||||
|
||||
describe('NoteCreateService', () => {
|
||||
let noteCreateService: NoteCreateService;
|
||||
|
||||
beforeAll(async () => {
|
||||
const app = await Test.createTestingModule({
|
||||
imports: [GlobalModule, CoreModule],
|
||||
}).compile();
|
||||
noteCreateService = app.get<NoteCreateService>(NoteCreateService);
|
||||
});
|
||||
|
||||
describe('is-renote', () => {
|
||||
const base: MiNote = {
|
||||
id: 'some-note-id',
|
||||
replyId: null,
|
||||
reply: null,
|
||||
renoteId: null,
|
||||
renote: null,
|
||||
threadId: null,
|
||||
text: null,
|
||||
name: null,
|
||||
cw: null,
|
||||
userId: 'some-user-id',
|
||||
user: null,
|
||||
localOnly: false,
|
||||
reactionAcceptance: null,
|
||||
renoteCount: 0,
|
||||
repliesCount: 0,
|
||||
clippedCount: 0,
|
||||
reactions: {},
|
||||
visibility: 'public',
|
||||
uri: null,
|
||||
url: null,
|
||||
fileIds: [],
|
||||
attachedFileTypes: [],
|
||||
visibleUserIds: [],
|
||||
mentions: [],
|
||||
mentionedRemoteUsers: '',
|
||||
reactionAndUserPairCache: [],
|
||||
emojis: [],
|
||||
tags: [],
|
||||
hasPoll: false,
|
||||
channelId: null,
|
||||
channel: null,
|
||||
userHost: null,
|
||||
replyUserId: null,
|
||||
replyUserHost: null,
|
||||
renoteUserId: null,
|
||||
renoteUserHost: null,
|
||||
};
|
||||
|
||||
const poll: IPoll = {
|
||||
choices: ['kinoko', 'takenoko'],
|
||||
multiple: false,
|
||||
expiresAt: null,
|
||||
};
|
||||
|
||||
const file: MiDriveFile = {
|
||||
id: 'some-file-id',
|
||||
userId: null,
|
||||
user: null,
|
||||
userHost: null,
|
||||
md5: '',
|
||||
name: '',
|
||||
type: '',
|
||||
size: 0,
|
||||
comment: null,
|
||||
blurhash: null,
|
||||
properties: {},
|
||||
storedInternal: false,
|
||||
url: '',
|
||||
thumbnailUrl: null,
|
||||
webpublicUrl: null,
|
||||
webpublicType: null,
|
||||
accessKey: null,
|
||||
thumbnailAccessKey: null,
|
||||
webpublicAccessKey: null,
|
||||
uri: null,
|
||||
src: null,
|
||||
folderId: null,
|
||||
folder: null,
|
||||
isSensitive: false,
|
||||
maybeSensitive: false,
|
||||
maybePorn: false,
|
||||
isLink: false,
|
||||
requestHeaders: null,
|
||||
requestIp: null,
|
||||
};
|
||||
|
||||
test('note without renote should not be Renote', () => {
|
||||
const note = { renote: null };
|
||||
expect(noteCreateService['isRenote'](note)).toBe(false);
|
||||
});
|
||||
|
||||
test('note with renote should be Renote and not be Quote', () => {
|
||||
const note = { renote: base };
|
||||
expect(noteCreateService['isRenote'](note)).toBe(true);
|
||||
expect(noteCreateService['isQuote'](note)).toBe(false);
|
||||
});
|
||||
|
||||
test('note with renote and text should be Quote', () => {
|
||||
const note = { renote: base, text: 'some-text' };
|
||||
expect(noteCreateService['isRenote'](note)).toBe(true);
|
||||
expect(noteCreateService['isQuote'](note)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renote and cw should be Quote', () => {
|
||||
const note = { renote: base, cw: 'some-cw' };
|
||||
expect(noteCreateService['isRenote'](note)).toBe(true);
|
||||
expect(noteCreateService['isQuote'](note)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renote and reply should be Quote', () => {
|
||||
const note = { renote: base, reply: { ...base, id: 'another-note-id' } };
|
||||
expect(noteCreateService['isRenote'](note)).toBe(true);
|
||||
expect(noteCreateService['isQuote'](note)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renote and poll should be Quote', () => {
|
||||
const note = { renote: base, poll };
|
||||
expect(noteCreateService['isRenote'](note)).toBe(true);
|
||||
expect(noteCreateService['isQuote'](note)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renote and non-empty files should be Quote', () => {
|
||||
const note = { renote: base, files: [file] };
|
||||
expect(noteCreateService['isRenote'](note)).toBe(true);
|
||||
expect(noteCreateService['isQuote'](note)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
import { MiNote } from '@/models/Note.js';
|
||||
|
||||
const base: MiNote = {
|
||||
id: 'some-note-id',
|
||||
replyId: null,
|
||||
reply: null,
|
||||
renoteId: null,
|
||||
renote: null,
|
||||
threadId: null,
|
||||
text: null,
|
||||
name: null,
|
||||
cw: null,
|
||||
userId: 'some-user-id',
|
||||
user: null,
|
||||
localOnly: false,
|
||||
reactionAcceptance: null,
|
||||
renoteCount: 0,
|
||||
repliesCount: 0,
|
||||
clippedCount: 0,
|
||||
reactions: {},
|
||||
visibility: 'public',
|
||||
uri: null,
|
||||
url: null,
|
||||
fileIds: [],
|
||||
attachedFileTypes: [],
|
||||
visibleUserIds: [],
|
||||
mentions: [],
|
||||
mentionedRemoteUsers: '',
|
||||
reactionAndUserPairCache: [],
|
||||
emojis: [],
|
||||
tags: [],
|
||||
hasPoll: false,
|
||||
channelId: null,
|
||||
channel: null,
|
||||
userHost: null,
|
||||
replyUserId: null,
|
||||
replyUserHost: null,
|
||||
renoteUserId: null,
|
||||
renoteUserHost: null,
|
||||
};
|
||||
|
||||
describe('misc:is-renote', () => {
|
||||
test('note without renoteId should not be Renote', () => {
|
||||
expect(isRenote(base)).toBe(false);
|
||||
});
|
||||
|
||||
test('note with renoteId should be Renote and not be Quote', () => {
|
||||
const note: MiNote = { ...base, renoteId: 'some-renote-id' };
|
||||
expect(isRenote(note)).toBe(true);
|
||||
expect(isQuote(note as any)).toBe(false);
|
||||
});
|
||||
|
||||
test('note with renoteId and text should be Quote', () => {
|
||||
const note: MiNote = { ...base, renoteId: 'some-renote-id', text: 'some-text' };
|
||||
expect(isRenote(note)).toBe(true);
|
||||
expect(isQuote(note as any)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renoteId and cw should be Quote', () => {
|
||||
const note: MiNote = { ...base, renoteId: 'some-renote-id', cw: 'some-cw' };
|
||||
expect(isRenote(note)).toBe(true);
|
||||
expect(isQuote(note as any)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renoteId and replyId should be Quote', () => {
|
||||
const note: MiNote = { ...base, renoteId: 'some-renote-id', replyId: 'some-reply-id' };
|
||||
expect(isRenote(note)).toBe(true);
|
||||
expect(isQuote(note as any)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renoteId and poll should be Quote', () => {
|
||||
const note: MiNote = { ...base, renoteId: 'some-renote-id', hasPoll: true };
|
||||
expect(isRenote(note)).toBe(true);
|
||||
expect(isQuote(note as any)).toBe(true);
|
||||
});
|
||||
|
||||
test('note with renoteId and non-empty fileIds should be Quote', () => {
|
||||
const note: MiNote = { ...base, renoteId: 'some-renote-id', fileIds: ['some-file-id'] };
|
||||
expect(isRenote(note)).toBe(true);
|
||||
expect(isQuote(note as any)).toBe(true);
|
||||
});
|
||||
});
|
|
@ -15,6 +15,16 @@ const fallbackName = (key: string) => `idbfallback::${key}`;
|
|||
|
||||
let idbAvailable = typeof window !== 'undefined' ? !!(window.indexedDB && window.indexedDB.open) : true;
|
||||
|
||||
// iframe.contentWindow.indexedDB.deleteDatabase() がchromeのバグで使用できないため、indexedDBを無効化している。
|
||||
// バグが治って再度有効化するのであれば、cypressのコマンド内のコメントアウトを外すこと
|
||||
// see https://github.com/misskey-dev/misskey/issues/13605#issuecomment-2053652123
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-expect-error
|
||||
if (window.Cypress) {
|
||||
idbAvailable = false;
|
||||
console.log('Cypress detected. It will use localStorage.');
|
||||
}
|
||||
|
||||
if (idbAvailable) {
|
||||
await iset('idb-test', 'test')
|
||||
.catch(err => {
|
||||
|
|
Loading…
Reference in New Issue