diff --git a/.eslintrc.json b/.eslintrc.json index 938f4e68e3..9a1264d8b8 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -4,7 +4,6 @@ "no-console": 1, "no-param-reassign": "error", "react-hooks/exhaustive-deps": "error" - }, "globals": { "fixture": false @@ -13,5 +12,13 @@ "react": { "version": "detect" } - } + }, + "overrides": [ + { + "files": ["*.spec.js[x]"], + "rules": { + "react/display-name": "off" + } + } + ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index a0f3ac2370..b857725bbb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## ✨ Features * Upgrade cozy-bar@7.20.1 to be able to call onSelect function +* Improve speed of search suggestion by preventing fetch notes url until click * Update cozy-stack-client and cozy-pouch-link to sync with cozy-client version * Update cozy-ui - Modify Viewers to handle [68.0.0 BC](https://github.com/cozy/cozy-ui/releases/tag/v68.0.0) diff --git a/src/drive/web/modules/services/components/SuggestionProvider.jsx b/src/drive/web/modules/services/components/SuggestionProvider.jsx index abfccb3873..b96da21af9 100644 --- a/src/drive/web/modules/services/components/SuggestionProvider.jsx +++ b/src/drive/web/modules/services/components/SuggestionProvider.jsx @@ -4,7 +4,6 @@ import FuzzyPathSearch from '../FuzzyPathSearch' import { withClient } from 'cozy-client' import { TYPE_DIRECTORY, makeNormalizedFile } from './helpers' -import { getIconUrl } from './iconContext' class SuggestionProvider extends React.Component { componentDidMount() { @@ -38,7 +37,7 @@ class SuggestionProvider extends React.Component { title: result.name, subtitle: result.path, term: result.name, - onSelect: 'open:' + result.url, + onSelect: result.onSelect || 'open:' + result.url, icon: result.icon })) }, @@ -54,7 +53,7 @@ class SuggestionProvider extends React.Component { return new Promise(async resolve => { const resp = await cozy.client.fetchJSON( 'GET', - `/data/io.cozy.files/_all_docs?include_docs=true` + '/data/io.cozy.files/_all_docs?include_docs=true' ) const files = resp.rows // TODO: fix me @@ -73,11 +72,8 @@ class SuggestionProvider extends React.Component { .filter(notInTrash) .filter(notOrphans) - const normalizedFiles = await Promise.all( - normalizedFilesPrevious.map( - async file => - await makeNormalizedFile(client, folders, file, getIconUrl) - ) + const normalizedFiles = normalizedFilesPrevious.map(file => + makeNormalizedFile(client, folders, file) ) this.fuzzyPathSearch = new FuzzyPathSearch(normalizedFiles) diff --git a/src/drive/web/modules/services/components/SuggestionProvider.spec.jsx b/src/drive/web/modules/services/components/SuggestionProvider.spec.jsx new file mode 100644 index 0000000000..1392f94244 --- /dev/null +++ b/src/drive/web/modules/services/components/SuggestionProvider.spec.jsx @@ -0,0 +1,98 @@ +import React from 'react' +import { render, waitFor } from '@testing-library/react' +import SuggestionProvider from './SuggestionProvider' +import { dummyFile, dummyNote } from 'test/dummies/dummyFile' + +const makeFileWithDoc = file => ({ ...file, doc: file }) +const parentFolder = makeFileWithDoc(dummyFile({ _id: 'id-file' })) +const folder = makeFileWithDoc(dummyFile({ dir_id: 'id-file' })) +const note = makeFileWithDoc( + dummyNote({ + dir_id: 'id-file', + name: 'name.cozy-note' + }) +) +const mockClient = { + fetchJSON: jest.fn().mockReturnValue({ rows: [parentFolder, folder, note] }) +} +const mockIntentAttributesClient = 'intent-attributes-client' + +jest.mock('cozy-client', () => ({ + ...jest.requireActual('cozy-client'), + withClient: Component => () => { + const intent = { + _id: 'id_intent', + attributes: { client: mockIntentAttributesClient } + } + return + } +})) +jest.mock('./iconContext', () => ({ getIconUrl: () => 'iconUrl' })) + +describe('SuggestionProvider', () => { + let events = {} + let event + + beforeEach(() => { + window.cozy.client = mockClient + window.addEventListener = jest.fn((event, callback) => { + events[event] = callback + }) + window.parent.postMessage = jest.fn() + event = { + origin: mockIntentAttributesClient, + data: { query: 'name', id: 'id' } + } + }) + + it('should query all files to display fuzzy suggestion', () => { + // Given + render() + + // When + events.message(event) + + // Then + expect(mockClient.fetchJSON).toHaveBeenCalledWith( + 'GET', + '/data/io.cozy.files/_all_docs?include_docs=true' + ) + }) + + it('should provide onSelect with open url when file is not a note + and function when it is a note', async () => { + // Given + render() + + // When + events.message(event) + + // Then + await waitFor(() => { + expect(window.parent.postMessage).toHaveBeenCalledWith( + { + id: 'id', + suggestions: [ + { + icon: 'iconUrl', + id: 'id-file', + onSelect: 'open:http://localhost/#/folder/id-file', + subtitle: '/path', + term: 'name', + title: 'name' + }, + { + icon: 'iconUrl', + id: 'id-file', + onSelect: 'id_note:id-file', + subtitle: '/path', + term: 'name.cozy-note', + title: 'name.cozy-note' + } + ], + type: 'intent-id_intent:data' + }, + 'intent-attributes-client' + ) + }) + }) +}) diff --git a/src/drive/web/modules/services/components/helpers.js b/src/drive/web/modules/services/components/helpers.js index a9e35a45a8..3247f0afc7 100644 --- a/src/drive/web/modules/services/components/helpers.js +++ b/src/drive/web/modules/services/components/helpers.js @@ -1,13 +1,26 @@ import { models } from 'cozy-client' +import { getIconUrl } from './iconContext' export const TYPE_DIRECTORY = 'directory' -export const makeNormalizedFile = async (client, folders, file, getIconUrl) => { +/** + * Normalize file for Front usage in component inside + * + * To reduce API call, the fetching of Note URL has been delayed + * inside an onSelect function called only if provided to + * see https://github.com/cozy/cozy-drive/pull/2663#discussion_r938671963 + * + * @param {CozyClient} client - cozy client instance + * @param {[IOCozyFile]} folders - all the folders returned by API + * @param {IOCozyFile} file - file to normalize + * @returns file with normalized field to be used in AutoSuggestion + */ +export const makeNormalizedFile = (client, folders, file) => { const isDir = file.type === TYPE_DIRECTORY const dirId = isDir ? file._id : file.dir_id const urlToFolder = `${window.location.origin}/#/folder/${dirId}` - let path, url + let path, url, onSelect if (isDir) { path = file.path url = urlToFolder @@ -15,7 +28,7 @@ export const makeNormalizedFile = async (client, folders, file, getIconUrl) => { const parentDir = folders.find(folder => folder._id === file.dir_id) path = parentDir && parentDir.path ? parentDir.path : '' if (models.file.isNote(file)) { - url = await models.note.fetchURL(client, file) + onSelect = `id_note:${file.id}` } else { url = `${urlToFolder}/file/${file._id}` } @@ -26,6 +39,7 @@ export const makeNormalizedFile = async (client, folders, file, getIconUrl) => { name: file.name, path, url, + onSelect, icon: getIconUrl(file) } } diff --git a/src/drive/web/modules/services/components/helpers.spec.js b/src/drive/web/modules/services/components/helpers.spec.js index 57244d2ee2..73e615a247 100644 --- a/src/drive/web/modules/services/components/helpers.spec.js +++ b/src/drive/web/modules/services/components/helpers.spec.js @@ -1,7 +1,6 @@ import { createMockClient, models } from 'cozy-client' import { makeNormalizedFile, TYPE_DIRECTORY } from './helpers' -import { getIconUrl } from './iconContext' jest.mock('./iconContext', () => ({ getIconUrl: () => 'iconUrl' })) models.note.fetchURL = jest.fn(() => 'noteUrl') @@ -19,7 +18,7 @@ const noteFileProps = { } describe('makeNormalizedFile', () => { - it('should return correct values for a directory', async () => { + it('should return correct values for a directory', () => { const folders = [] const file = { _id: 'fileId', @@ -28,14 +27,10 @@ describe('makeNormalizedFile', () => { name: 'fileName' } - const normalizedFile = await makeNormalizedFile( - client, - folders, - file, - getIconUrl - ) + const normalizedFile = makeNormalizedFile(client, folders, file) - expect(normalizedFile).toMatchObject({ + expect(normalizedFile).toEqual({ + icon: 'iconUrl', id: 'fileId', name: 'fileName', path: 'filePath', @@ -43,7 +38,7 @@ describe('makeNormalizedFile', () => { }) }) - it('should return correct values for a file', async () => { + it('should return correct values for a file', () => { const folders = [{ _id: 'folderId', path: 'folderPath' }] const file = { _id: 'fileId', @@ -52,14 +47,10 @@ describe('makeNormalizedFile', () => { name: 'fileName' } - const normalizedFile = await makeNormalizedFile( - client, - folders, - file, - getIconUrl - ) + const normalizedFile = makeNormalizedFile(client, folders, file) - expect(normalizedFile).toMatchObject({ + expect(normalizedFile).toEqual({ + icon: 'iconUrl', id: 'fileId', name: 'fileName', path: 'folderPath', @@ -67,28 +58,47 @@ describe('makeNormalizedFile', () => { }) }) - it('should return correct values for a note', async () => { + it('should return correct values for a note with on Select function - better for performance', () => { const folders = [{ _id: 'folderId', path: 'folderPath' }] const file = { _id: 'fileId', + id: 'noteId', dir_id: 'folderId', type: 'file', name: 'fileName', ...noteFileProps } - const normalizedFile = await makeNormalizedFile( - client, - folders, - file, - getIconUrl - ) + const normalizedFile = makeNormalizedFile(client, folders, file) - expect(normalizedFile).toMatchObject({ + expect(normalizedFile).toEqual({ + icon: 'iconUrl', id: 'fileId', name: 'note.cozy-note', path: 'folderPath', - url: 'noteUrl' + onSelect: 'id_note:noteId' + }) + }) + + it('should not return filled onSelect for a note without metadata', () => { + const folders = [{ _id: 'folderId', path: 'folderPath' }] + const file = { + _id: 'fileId', + id: 'noteId', + dir_id: 'folderId', + type: 'file', + name: 'note.cozy-note' + } + + const normalizedFile = makeNormalizedFile(client, folders, file) + + expect(normalizedFile).toEqual({ + icon: 'iconUrl', + id: 'fileId', + name: 'note.cozy-note', + path: 'folderPath', + onSelect: undefined, + url: 'http://localhost/#/folder/folderId/file/fileId' }) }) }) diff --git a/test/dummies/dummyFile.js b/test/dummies/dummyFile.js new file mode 100644 index 0000000000..95abe3927f --- /dev/null +++ b/test/dummies/dummyFile.js @@ -0,0 +1,37 @@ +// eslint-disable-next-line no-unused-vars +const { IOCozyFile } = require('cozy-client/dist/types') + +/** + * Create a dummy file, with overridden value of given param + * + * @param {Partial} [file={}] - optional file with value to keep + * @returns {IOCozyFile} a dummy file + */ +export const dummyFile = file => ({ + _id: 'id-file', + _type: 'doctype-file', + name: 'name', + id: 'id-file', + icon: 'icon', + path: '/path', + type: 'directory', + ...file +}) + +/** + * Create a dummy note, with overridden value of given param + * + * @param {Partial} [note={}] + * @returns {IOCozyFile} a dummy note + */ +export const dummyNote = note => ({ + ...dummyFile(), + type: 'file', + metadata: { + content: '', + schema: '', + title: '', + version: '' + }, + ...note +})