Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 50 additions & 3 deletions apps/files/src/actions/favoriteAction.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import type { IFolder, IView } from '@nextcloud/files'

import axios from '@nextcloud/axios'
import * as eventBus from '@nextcloud/event-bus'
import { File, Permission } from '@nextcloud/files'
import { File, Folder, Permission } from '@nextcloud/files'
import { beforeAll, beforeEach, describe, expect, test, vi } from 'vitest'
import { logger } from '../utils/logger.ts'
import { action } from './favoriteAction.ts'
Expand All @@ -26,6 +26,20 @@ const favoriteView = {
name: 'Favorites',
} as IView

const favoritesRootFolder = new Folder({
id: 0,
source: 'http://localhost/remote.php/dav/files/admin',
owner: 'admin',
root: '/files/admin',
})

const nestedFolder = new Folder({
id: 2,
source: 'http://localhost/remote.php/dav/files/admin/Foo/Bar',
owner: 'admin',
root: '/files/admin',
})

// Mock webroot variable
beforeAll(() => {
window.OC = {
Expand Down Expand Up @@ -272,7 +286,7 @@ describe('Favorite action execute tests', () => {
const exec = await action.exec({
nodes: [file],
view: favoriteView,
folder: {} as IFolder,
folder: favoritesRootFolder,
contents: [],
})

Expand All @@ -289,6 +303,38 @@ describe('Favorite action execute tests', () => {
expect(eventBus.emit).toHaveBeenCalledWith('files:favorites:removed', file)
})

test('Favorite triggers node removal if favorite view root even for nested files', async () => {
vi.spyOn(axios, 'post')
vi.spyOn(eventBus, 'emit')

const file = new File({
id: 1,
source: 'http://localhost/remote.php/dav/files/admin/Foo/Bar/foobar.txt',
root: '/files/admin',
owner: 'admin',
mime: 'text/plain',
attributes: {
favorite: 1,
},
})

const exec = await action.exec({
nodes: [file],
view: favoriteView,
folder: favoritesRootFolder,
contents: [],
})

expect(exec).toBe(true)

expect(axios.post).toBeCalledTimes(1)
expect(axios.post).toBeCalledWith('/index.php/apps/files/api/v1/files/Foo/Bar/foobar.txt', { tags: [] })

expect(file.attributes.favorite).toBe(0)
expect(eventBus.emit).toHaveBeenCalledWith('files:node:deleted', file)
expect(eventBus.emit).toHaveBeenCalledWith('files:favorites:removed', file)
})

test('Favorite does NOT triggers node removal if favorite view but NOT root dir', async () => {
vi.spyOn(axios, 'post')
vi.spyOn(eventBus, 'emit')
Expand All @@ -307,7 +353,7 @@ describe('Favorite action execute tests', () => {
const exec = await action.exec({
nodes: [file],
view: favoriteView,
folder: {} as IFolder,
folder: nestedFolder,
contents: [],
})

Expand All @@ -321,6 +367,7 @@ describe('Favorite action execute tests', () => {
expect(file.attributes.favorite).toBe(0)
expect(eventBus.emit).toHaveBeenCalled()
expect(eventBus.emit).toBeCalledWith('files:favorites:removed', file)
expect(eventBus.emit).not.toHaveBeenCalledWith('files:node:deleted', file)
})

test('Favorite fails and show error', async () => {
Expand Down
28 changes: 18 additions & 10 deletions apps/files/src/actions/favoriteAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

import type { IFileAction, INode, IView } from '@nextcloud/files'
import type { IFileAction, IFolder, INode, IView } from '@nextcloud/files'

import StarOutlineSvg from '@mdi/svg/svg/star-outline.svg?raw'
import StarSvg from '@mdi/svg/svg/star.svg?raw'
Expand Down Expand Up @@ -47,11 +47,11 @@ export const action: IFileAction = {
&& nodes.every((node) => node.permissions !== Permission.NONE)
},

async exec({ nodes, view }): Promise<boolean> {
async exec({ nodes, view, folder }): Promise<boolean> {
const willFavorite = shouldFavorite([nodes[0]])
return await favoriteNode(nodes[0], view, willFavorite)
return await favoriteNode(nodes[0], view, willFavorite, folder)
},
async execBatch({ nodes, view }): Promise<boolean[]> {
async execBatch({ nodes, view, folder }): Promise<boolean[]> {
const willFavorite = shouldFavorite(nodes)

// Map each node to a promise that resolves with the result of exec(node)
Expand All @@ -60,7 +60,7 @@ export const action: IFileAction = {
const promise = new Promise<boolean>((resolve) => {
queue.add(async () => {
try {
await favoriteNode(node, view, willFavorite)
await favoriteNode(node, view, willFavorite, folder)
resolve(true)
} catch (error) {
logger.error('Error while adding file to favorite', { error, source: node.source, node })
Expand Down Expand Up @@ -88,8 +88,9 @@ export const action: IFileAction = {
* @param node - The node to favorite/unfavorite
* @param view - The current view
* @param willFavorite - Whether to favorite or unfavorite the node
* @param folder - The currently open folder
*/
export async function favoriteNode(node: INode, view: IView, willFavorite: boolean): Promise<boolean> {
export async function favoriteNode(node: INode, view: IView, willFavorite: boolean, folder?: IFolder): Promise<boolean> {
try {
// TODO: migrate to webdav tags plugin
const url = generateUrl('/apps/files/api/v1/files') + encodePath(node.path)
Expand All @@ -99,10 +100,8 @@ export async function favoriteNode(node: INode, view: IView, willFavorite: boole
: [],
})

// Let's delete if we are in the favourites view
// AND if it is removed from the user favorites
// AND it's in the root of the favorites view
if (view.id === 'favorites' && !willFavorite && node.dirname === '/') {
// Remove from the virtual favorites listing, not when browsing a real folder
if (view.id === 'favorites' && !willFavorite && isFavoritesRoot(folder)) {
emit('files:node:deleted', node)
}

Expand Down Expand Up @@ -133,3 +132,12 @@ export async function favoriteNode(node: INode, view: IView, willFavorite: boole
function shouldFavorite(nodes: INode[]): boolean {
return nodes.some((node) => node.attributes.favorite !== 1)
}

/**
* Whether the current folder is the virtual root of the favorites view.
*
* @param folder - The currently open folder
*/
function isFavoritesRoot(folder?: IFolder): boolean {
return !folder?.path || folder.path === '/'
}
4 changes: 2 additions & 2 deletions apps/files/src/actions/sidebarFavoriteAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ export function registerSidebarFavoriteAction() {
return starOutlineSvg
},

onClick({ node, view }) {
favoriteNode(node, view, !node.attributes.favorite)
onClick({ node, view, folder }) {
favoriteNode(node, view, !node.attributes.favorite, folder)
},
})
}
3 changes: 2 additions & 1 deletion apps/files/src/views/favorites.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,8 @@ describe('Dynamic update of favorite folders', () => {
contents: [],
})

expect(eventBus.emit).toHaveBeenCalledTimes(2)
expect(eventBus.emit).toHaveBeenCalledTimes(3)
expect(eventBus.emit).toHaveBeenCalledWith('files:node:deleted', folder)
expect(eventBus.emit).toHaveBeenCalledWith('files:favorites:removed', folder)
expect(eventBus.emit).toHaveBeenCalledWith('files:node:updated', folder)
expect(fo).toHaveBeenCalled()
Expand Down
Loading