pulsar/spec/extensions/tree-view-spec.coffee

666 lines
27 KiB
CoffeeScript

$ = require 'jquery'
_ = require 'underscore'
TreeView = require 'tree-view'
RootView = require 'root-view'
Directory = require 'directory'
Native = require 'native'
fs = require 'fs'
describe "TreeView", ->
[rootView, project, treeView, sampleJs, sampleTxt] = []
beforeEach ->
rootView = new RootView(require.resolve('fixtures/'))
project = rootView.project
rootView.activateExtension(TreeView)
treeView = rootView.find(".tree-view").view()
treeView.root = treeView.find('> li:first').view()
sampleJs = treeView.find('.file:contains(sample.js)')
sampleTxt = treeView.find('.file:contains(sample.txt)')
expect(treeView.root.directory.subscriptionCount()).toBeGreaterThan 0
afterEach ->
treeView.deactivate()
describe ".initialize(project)", ->
it "renders the root of the project and its contents alphabetically with subdirectories first in a collapsed state", ->
expect(treeView.root.find('> .header .disclosure-arrow')).toHaveText('')
expect(treeView.root.find('> .header .name')).toHaveText('fixtures/')
rootEntries = treeView.root.find('.entries')
subdir1 = rootEntries.find('> li:eq(0)')
expect(subdir1.find('.disclosure-arrow')).toHaveText('')
expect(subdir1.find('.name')).toHaveText('dir/')
expect(subdir1.find('.entries')).not.toExist()
subdir2 = rootEntries.find('> li:eq(1)')
expect(subdir2.find('.disclosure-arrow')).toHaveText('')
expect(subdir2.find('.name')).toHaveText('zed/')
expect(subdir2.find('.entries')).not.toExist()
expect(rootEntries.find('> .file:contains(sample.js)')).toExist()
expect(rootEntries.find('> .file:contains(sample.txt)')).toExist()
it "selects the rootview", ->
expect(treeView.selectedEntry()).toEqual treeView.root
describe "when the project has no path", ->
beforeEach ->
treeView.deactivate()
rootView = new RootView
rootView.activateExtension(TreeView)
treeView = rootView.find(".tree-view").view()
it "does not create a root node", ->
expect(treeView.root).not.toExist()
it "creates a root view when the project path is created", ->
rootView.open(require.resolve('fixtures/sample.js'))
expect(treeView.root.getPath()).toBe require.resolve('fixtures')
expect(treeView.root.parent()).toMatchSelector(".tree-view")
oldRoot = treeView.root
rootView.project.setPath('/tmp')
expect(treeView.root).not.toEqual oldRoot
expect(oldRoot.hasParent()).toBeFalsy()
describe "serialization", ->
newTreeView = null
afterEach ->
newTreeView.deactivate()
it "restores expanded directories and selected file when deserialized", ->
treeView.find('.directory:contains(zed)').click()
sampleJs.click()
newRootView = RootView.deserialize(rootView.serialize())
newRootView.activateExtension(TreeView)
newTreeView = newRootView.find(".tree-view").view()
expect(newTreeView).toExist()
expect(newTreeView.selectedEntry()).toMatchSelector(".file:contains(sample.js)")
expect(newTreeView.find(".directory:contains(zed)")).toHaveClass("expanded")
it "restores the focus state of the tree view", ->
treeView.attachToDom()
treeView.focus()
expect(treeView).toMatchSelector ':focus'
newRootView = RootView.deserialize(rootView.serialize())
rootView.remove()
newRootView.attachToDom()
newRootView.activateExtension(TreeView)
newTreeView = newRootView.find(".tree-view").view()
expect(newTreeView).toMatchSelector ':focus'
describe "when tree-view:toggle is triggered on the root view", ->
it "shows/hides the tree view", ->
rootView.attachToDom()
treeView.focus()
expect(treeView.hasParent()).toBeTruthy()
rootView.trigger 'tree-view:toggle'
expect(treeView.hasParent()).toBeFalsy()
expect(rootView).toMatchSelector(':focus')
rootView.trigger 'tree-view:toggle'
expect(treeView.hasParent()).toBeTruthy()
expect(treeView).toMatchSelector(':focus')
describe "when a directory's disclosure arrow is clicked", ->
it "expands / collapses the associated directory", ->
subdir = treeView.root.find('.entries > li:contains(dir/)').view()
expect(subdir.disclosureArrow).toHaveText('')
expect(subdir.find('.entries')).not.toExist()
subdir.disclosureArrow.click()
expect(subdir.disclosureArrow).toHaveText('')
expect(subdir.find('.entries')).toExist()
subdir.disclosureArrow.click()
expect(subdir.disclosureArrow).toHaveText('')
expect(subdir.find('.entries')).not.toExist()
it "restores the expansion state of descendant directories", ->
child = treeView.root.find('.entries > li:contains(dir/)').view()
child.disclosureArrow.click()
grandchild = child.find('.entries > li:contains(a-dir/)').view()
grandchild.disclosureArrow.click()
treeView.root.disclosureArrow.click()
expect(treeView.root.find('.entries')).not.toExist()
treeView.root.disclosureArrow.click()
# previously expanded descendants remain expanded
expect(treeView.root.find('> .entries > li:contains(dir/) > .entries > li:contains(a-dir/) > .entries').length).toBe 1
# collapsed descendants remain collapsed
expect(treeView.root.find('> .entries > li.contains(zed/) > .entries')).not.toExist()
it "when collapsing a directory, removes change subscriptions from the collapsed directory and its descendants", ->
child = treeView.root.entries.find('li:contains(dir/)').view()
child.disclosureArrow.click()
grandchild = child.entries.find('li:contains(a-dir/)').view()
grandchild.disclosureArrow.click()
expect(treeView.root.directory.subscriptionCount()).toBe 1
expect(child.directory.subscriptionCount()).toBe 1
expect(grandchild.directory.subscriptionCount()).toBe 1
treeView.root.disclosureArrow.click()
expect(treeView.root.directory.subscriptionCount()).toBe 0
expect(child.directory.subscriptionCount()).toBe 0
expect(grandchild.directory.subscriptionCount()).toBe 0
describe "when a file is single-clicked", ->
it "selects the files and opens it in the active editor, without changing focus", ->
expect(rootView.activeEditor()).toBeUndefined()
sampleJs.trigger clickEvent(originalEvent: { detail: 1 })
expect(sampleJs).toHaveClass 'selected'
expect(rootView.activeEditor().buffer.path).toBe require.resolve('fixtures/sample.js')
expect(rootView.activeEditor().isFocused).toBeFalsy()
sampleTxt.trigger clickEvent(originalEvent: { detail: 1 })
expect(sampleTxt).toHaveClass 'selected'
expect(treeView.find('.selected').length).toBe 1
expect(rootView.activeEditor().buffer.path).toBe require.resolve('fixtures/sample.txt')
expect(rootView.activeEditor().isFocused).toBeFalsy()
describe "when a file is double-clicked", ->
it "selects the file and opens it in the active editor on the first click, then changes focus to the active editor on the second", ->
sampleJs.trigger clickEvent(originalEvent: { detail: 1 })
expect(sampleJs).toHaveClass 'selected'
expect(rootView.activeEditor().buffer.path).toBe require.resolve('fixtures/sample.js')
expect(rootView.activeEditor().isFocused).toBeFalsy()
sampleJs.trigger clickEvent(originalEvent: { detail: 2 })
expect(rootView.activeEditor().isFocused).toBeTruthy()
describe "when a directory is single-clicked", ->
it "is selected", ->
subdir = treeView.root.find('.directory:first').view()
subdir.trigger clickEvent(originalEvent: { detail: 1 })
expect(subdir).toHaveClass 'selected'
describe "when a directory is double-clicked", ->
it "toggles the directory expansion state and does not change the focus to the editor", ->
sampleJs.trigger clickEvent(originalEvent: { detail: 1 })
subdir = treeView.root.find('.directory:first').view()
subdir.trigger clickEvent(originalEvent: { detail: 1 })
expect(subdir).toHaveClass 'selected'
subdir.trigger clickEvent(originalEvent: { detail: 2 })
expect(subdir).toHaveClass 'expanded'
expect(rootView.activeEditor().isFocused).toBeFalsy()
describe "when a new file is opened in the active editor", ->
it "is selected in the tree view if the file's entry visible", ->
sampleJs.click()
rootView.open(require.resolve('fixtures/sample.txt'))
expect(sampleTxt).toHaveClass 'selected'
expect(treeView.find('.selected').length).toBe 1
it "selected a file's parent dir if the file's entry is not visible", ->
# treeView.attachToDom()
rootView.open(require.resolve('fixtures/dir/a-dir/oh-git'))
dirView = treeView.root.find('.directory:contains(dir)').view()
expect(dirView).toHaveClass 'selected'
# dirView.expand()
# aDirView = dirView.find('.directory:contains(a-dir)').view()
# expect(aDirView).toHaveClass 'selected'
describe "when a different editor becomes active", ->
it "selects the file in that is open in that editor", ->
sampleJs.click()
leftEditor = rootView.activeEditor()
rightEditor = leftEditor.splitRight()
sampleTxt.click()
expect(sampleTxt).toHaveClass('selected')
leftEditor.focus()
expect(sampleJs).toHaveClass('selected')
describe "keyboard navigation", ->
afterEach ->
expect(treeView.find('.selected').length).toBeLessThan 2
describe "move-down", ->
describe "when a collapsed directory is selected", ->
it "skips to the next directory", ->
treeView.root.find('.directory:eq(0)').click()
treeView.trigger 'move-down'
expect(treeView.root.find('.directory:eq(1)')).toHaveClass 'selected'
describe "when an expanded directory is selected", ->
it "selects the first entry of the directory", ->
subdir = treeView.root.find('.directory:eq(1)').view()
subdir.expand()
subdir.click()
treeView.trigger 'move-down'
expect(subdir.entries.find('.entry:first')).toHaveClass 'selected'
describe "when the last entry of an expanded directory is selected", ->
it "selects the entry after its parent directory", ->
subdir1 = treeView.root.find('.directory:eq(1)').view()
subdir1.expand()
subdir1.entries.find('.entry:last').click()
treeView.trigger 'move-down'
expect(treeView.root.find('.entries > .entry:eq(2)')).toHaveClass 'selected'
describe "when the last entry of the last directory is selected", ->
it "does not change the selection", ->
lastEntry = treeView.root.find('> .entries .entry:last')
lastEntry.click()
treeView.trigger 'move-down'
expect(lastEntry).toHaveClass 'selected'
describe "move-up", ->
describe "when there is an expanded directory before the currently selected entry", ->
it "selects the last entry in the expanded directory", ->
lastDir = treeView.root.find('.directory:last').view()
fileAfterDir = lastDir.next().view()
lastDir.expand()
fileAfterDir.click()
treeView.trigger 'move-up'
expect(lastDir.find('.entry:last')).toHaveClass 'selected'
describe "when there is an entry before the currently selected entry", ->
it "selects the previous entry", ->
lastEntry = treeView.root.find('.entry:last')
lastEntry.click()
treeView.trigger 'move-up'
expect(lastEntry.prev()).toHaveClass 'selected'
describe "when there is no entry before the currently selected entry, but there is a parent directory", ->
it "selects the parent directory", ->
subdir = treeView.root.find('.directory:first').view()
subdir.expand()
subdir.find('> .entries > .entry:first').click()
treeView.trigger 'move-up'
expect(subdir).toHaveClass 'selected'
describe "when there is no parent directory or previous entry", ->
it "does not change the selection", ->
treeView.root.click()
treeView.trigger 'move-up'
expect(treeView.root).toHaveClass 'selected'
describe "movement outside of viewable region", ->
it "scrolls the tree view to the selected item", ->
treeView.height(100)
treeView.attachToDom()
$(element).view().expand() for element in treeView.find('.directory')
expect(treeView.prop('scrollHeight')).toBeGreaterThan treeView.outerHeight()
treeView.moveDown()
expect(treeView.scrollTop()).toBe 0
entryCount = treeView.find(".entry").length
_.times entryCount, -> treeView.moveDown()
expect(treeView.scrollBottom()).toBe treeView.prop('scrollHeight')
_.times entryCount, -> treeView.moveUp()
expect(treeView.scrollTop()).toBe 0
describe "tree-view:expand-directory", ->
describe "when a directory entry is selected", ->
it "expands the current directory", ->
subdir = treeView.root.find('.directory:first')
subdir.click()
expect(subdir).not.toHaveClass 'expanded'
treeView.trigger 'tree-view:expand-directory'
expect(subdir).toHaveClass 'expanded'
describe "when a file entry is selected", ->
it "does nothing", ->
treeView.root.find('.file').click()
treeView.trigger 'tree-view:expand-directory'
describe "tree-view:collapse-directory", ->
subdir = null
beforeEach ->
subdir = treeView.root.find('> .entries > .directory').eq(0).view()
subdir.expand()
describe "when an expanded directory is selected", ->
it "collapses the selected directory", ->
expect(subdir).toHaveClass 'expanded'
subdir.click()
treeView.trigger 'tree-view:collapse-directory'
expect(subdir).not.toHaveClass 'expanded'
expect(treeView.root).toHaveClass 'expanded'
describe "when a collapsed directory is selected", ->
it "collapses and selects the selected directory's parent directory", ->
subdir.find('.directory').click()
treeView.trigger 'tree-view:collapse-directory'
expect(subdir).not.toHaveClass 'expanded'
expect(subdir).toHaveClass 'selected'
expect(treeView.root).toHaveClass 'expanded'
describe "when collapsed root directory is selected", ->
it "does not raise an error", ->
treeView.root.collapse()
treeView.selectEntry(treeView.root)
treeView.trigger 'tree-view:collapse-directory'
describe "when a file is selected", ->
it "collapses and selects the selected file's parent directory", ->
subdir.find('.file').click()
treeView.trigger 'tree-view:collapse-directory'
expect(subdir).not.toHaveClass 'expanded'
expect(subdir).toHaveClass 'selected'
expect(treeView.root).toHaveClass 'expanded'
describe "tree-view:open-selected-entry", ->
describe "when a file is selected", ->
it "opens the file in the editor", ->
treeView.root.find('.file:contains(sample.js)').click()
treeView.root.trigger 'tree-view:open-selected-entry'
expect(rootView.activeEditor().buffer.path).toBe require.resolve('fixtures/sample.js')
describe "when a directory is selected", ->
it "expands or collapses the directory", ->
subdir = treeView.root.find('.directory').first()
subdir.click()
expect(subdir).not.toHaveClass 'expanded'
treeView.root.trigger 'tree-view:open-selected-entry'
expect(subdir).toHaveClass 'expanded'
treeView.root.trigger 'tree-view:open-selected-entry'
expect(subdir).not.toHaveClass 'expanded'
describe "when nothing is selected", ->
it "does nothing", ->
treeView.root.trigger 'tree-view:open-selected-entry'
expect(rootView.activeEditor()).toBeUndefined()
describe "file modification", ->
[dirView, fileView, rootDirPath, dirPath, filePath] = []
beforeEach ->
treeView.deactivate()
rootDirPath = "/tmp/atom-tests"
fs.remove(rootDirPath) if fs.exists(rootDirPath)
dirPath = fs.join(rootDirPath, "test-dir")
filePath = fs.join(dirPath, "test-file.txt")
fs.makeDirectory(rootDirPath)
fs.makeDirectory(dirPath)
fs.write(filePath, "doesn't matter")
rootView = new RootView(rootDirPath)
project = rootView.project
treeView = new TreeView(rootView)
treeView.root = treeView.root
dirView = treeView.root.entries.find('.directory:contains(test-dir)').view()
dirView.expand()
fileView = treeView.find('.file:contains(test-file.txt)').view()
afterEach ->
fs.remove(rootDirPath) if fs.exists(rootDirPath)
describe "tree-view:add", ->
addDialog = null
beforeEach ->
fileView.click()
treeView.trigger "tree-view:add"
addDialog = rootView.find(".tree-view-dialog").view()
describe "when a file is selected", ->
it "opens an add dialog with the file's current directory path populated", ->
expect(addDialog).toExist()
expect(addDialog.prompt.text()).toBeTruthy()
expect(project.relativize(dirPath)).toMatch(/[^\/]$/)
expect(addDialog.miniEditor.getText()).toBe(project.relativize(dirPath) + "/")
expect(addDialog.miniEditor.getCursorBufferPosition().column).toBe addDialog.miniEditor.getText().length
expect(addDialog.miniEditor.isFocused).toBeTruthy()
describe "when parent directory of the selected file changes", ->
it "active file is still shown as selected in the tree view", ->
directoryChangeHandler = jasmine.createSpy("directory-change")
dirView.on "tree-view:directory-modified", directoryChangeHandler
dirView.directory.trigger 'contents-change'
expect(directoryChangeHandler).toHaveBeenCalled()
expect(treeView.find('.selected').text()).toBe fs.base(filePath)
describe "when the path without a trailing '/' is changed and confirmed", ->
describe "when no file exists at that location", ->
it "add a file, closes the dialog and selects the file in the tree-view", ->
newPath = fs.join(dirPath, "new-test-file.txt")
addDialog.miniEditor.insertText(fs.base(newPath))
addDialog.trigger 'tree-view:confirm'
expect(fs.exists(newPath)).toBeTruthy()
expect(fs.isFile(newPath)).toBeTruthy()
expect(addDialog.parent()).not.toExist()
expect(rootView.activeEditor().buffer.path).toBe newPath
waitsFor "tree view to be updated", ->
dirView.entries.find("> .file").length > 1
runs ->
expect(treeView.find('.selected').text()).toBe fs.base(newPath)
describe "when a file already exists at that location", ->
it "shows an error message and does not close the dialog", ->
newPath = fs.join(dirPath, "new-test-file.txt")
fs.write(newPath, '')
addDialog.miniEditor.insertText(fs.base(newPath))
addDialog.trigger 'tree-view:confirm'
expect(addDialog.prompt.text()).toContain 'Error'
expect(addDialog.prompt.text()).toContain 'already exists'
expect(addDialog.prompt).toHaveClass('error')
expect(addDialog.hasParent()).toBeTruthy()
describe "when the path with a trailing '/' is changed and confirmed", ->
describe "when no file or directory exists at the given path", ->
it "adds a directory and closes the dialog", ->
newPath = fs.join(dirPath, "new-dir")
addDialog.miniEditor.insertText("new-dir/")
addDialog.trigger 'tree-view:confirm'
expect(fs.exists(newPath)).toBeTruthy()
expect(fs.isDirectory(newPath)).toBeTruthy()
expect(addDialog.parent()).not.toExist()
expect(rootView.activeEditor().buffer.path).not.toBe newPath
describe "when a or directory already exists at the given path", ->
it "shows an error message and does not close the dialog", ->
newPath = fs.join(dirPath, "new-dir")
fs.makeDirectory(newPath)
addDialog.miniEditor.insertText("new-dir/")
addDialog.trigger 'tree-view:confirm'
expect(addDialog.prompt.text()).toContain 'Error'
expect(addDialog.prompt.text()).toContain 'already exists'
expect(addDialog.prompt).toHaveClass('error')
expect(addDialog.hasParent()).toBeTruthy()
describe "when 'tree-view:cancel' is triggered on the add dialog", ->
it "removes the dialog and focuses the tree view", ->
treeView.attachToDom()
addDialog.trigger 'tree-view:cancel'
expect(addDialog.parent()).not.toExist()
expect(treeView).toMatchSelector(':focus')
describe "when the add dialog's editor loses focus", ->
it "removes the dialog and focuses root view", ->
rootView.attachToDom()
rootView.focus()
expect(addDialog.parent()).not.toExist()
expect(rootView.activeEditor().isFocused).toBeTruthy()
describe "when a directory is selected", ->
it "opens an add dialog with the directory's path populated", ->
addDialog.cancel()
dirView.click()
treeView.trigger "tree-view:add"
addDialog = rootView.find(".tree-view-dialog").view()
expect(addDialog).toExist()
expect(addDialog.prompt.text()).toBeTruthy()
expect(project.relativize(dirPath)).toMatch(/[^\/]$/)
expect(addDialog.miniEditor.getText()).toBe(project.relativize(dirPath) + "/")
expect(addDialog.miniEditor.getCursorBufferPosition().column).toBe addDialog.miniEditor.getText().length
expect(addDialog.miniEditor.isFocused).toBeTruthy()
describe "when the root directory is selected", ->
it "opens an add dialog with no path populated", ->
addDialog.cancel()
treeView.root.click()
treeView.trigger "tree-view:add"
addDialog = rootView.find(".tree-view-dialog").view()
expect(addDialog.miniEditor.getText().length).toBe 0
describe "tree-view:move", ->
describe "when a file is selected", ->
moveDialog = null
beforeEach ->
fileView.click()
treeView.trigger "tree-view:move"
moveDialog = rootView.find(".tree-view-dialog").view()
it "opens a move dialog with the file's current path (excluding extension) populated", ->
extension = fs.extension(filePath)
fileNameWithoutExtension = fs.base(filePath, extension)
expect(moveDialog).toExist()
expect(moveDialog.prompt.text()).toBe "Enter the new path for the file:"
expect(moveDialog.miniEditor.getText()).toBe(project.relativize(filePath))
expect(moveDialog.miniEditor.getSelectedText()).toBe fs.base(fileNameWithoutExtension)
expect(moveDialog.miniEditor.isFocused).toBeTruthy()
describe "when the path is changed and confirmed", ->
describe "when all the directories along the new path exist", ->
it "moves the file, updates the tree view, and closes the dialog", ->
runs ->
newPath = fs.join(rootDirPath, 'renamed-test-file.txt')
moveDialog.miniEditor.setText(newPath)
moveDialog.trigger 'tree-view:confirm'
expect(fs.exists(newPath)).toBeTruthy()
expect(fs.exists(filePath)).toBeFalsy()
expect(moveDialog.parent()).not.toExist()
waitsFor "tree view to update", ->
treeView.root.find('> .entries > .file:contains(renamed-test-file.txt)').length > 0
runs ->
dirView = treeView.root.entries.find('.directory:contains(test-dir)').view()
dirView.expand()
expect(dirView.entries.children().length).toBe 0
describe "when the directories along the new path don't exist", ->
it "creates the target directory before moving the file", ->
runs ->
newPath = fs.join(rootDirPath, 'new-directory', 'renamed-test-file.txt')
moveDialog.miniEditor.setText(newPath)
moveDialog.trigger 'tree-view:confirm'
expect(fs.exists(newPath)).toBeTruthy()
expect(fs.exists(filePath)).toBeFalsy()
describe "when a file or directory already exists at the target path", ->
it "shows an error message and does not close the dialog", ->
runs ->
fs.write(fs.join(rootDirPath, 'target.txt'), '')
newPath = fs.join(rootDirPath, 'target.txt')
moveDialog.miniEditor.setText(newPath)
moveDialog.trigger 'tree-view:confirm'
expect(moveDialog.prompt.text()).toContain 'Error'
expect(moveDialog.prompt.text()).toContain 'already exists'
expect(moveDialog.prompt).toHaveClass('error')
expect(moveDialog.hasParent()).toBeTruthy()
describe "when 'tree-view:cancel' is triggered on the move dialog", ->
it "removes the dialog and focuses the tree view", ->
treeView.attachToDom()
moveDialog.trigger 'tree-view:cancel'
expect(moveDialog.parent()).not.toExist()
expect(treeView).toMatchSelector(':focus')
describe "when the move dialog's editor loses focus", ->
it "removes the dialog and focuses root view", ->
rootView.attachToDom()
rootView.focus()
expect(moveDialog.parent()).not.toExist()
expect(rootView.activeEditor().isFocused).toBeTruthy()
describe "tree-view:remove", ->
it "shows the native alert dialog", ->
fileView.click()
spyOn(Native, 'alert')
treeView.trigger 'tree-view:remove'
expect(Native.alert).toHaveBeenCalled()
describe "file system events", ->
temporaryFilePath = null
beforeEach ->
temporaryFilePath = fs.join(require.resolve('fixtures'), 'temporary')
if fs.exists(temporaryFilePath)
fs.remove(temporaryFilePath)
waits(20)
afterEach ->
fs.remove(temporaryFilePath) if fs.exists(temporaryFilePath)
describe "when a file is added or removed in an expanded directory", ->
it "updates the directory view to display the directory's new contents", ->
entriesCountBefore = null
runs ->
expect(fs.exists(temporaryFilePath)).toBeFalsy()
entriesCountBefore = treeView.root.entries.find('.entry').length
fs.write temporaryFilePath, 'hi'
waitsFor "directory view contens to refresh", ->
treeView.root.entries.find('.entry').length == entriesCountBefore + 1
runs ->
expect(treeView.root.entries.find('.entry').length).toBe entriesCountBefore + 1
expect(treeView.root.entries.find('.file:contains(temporary)')).toExist()
fs.remove(temporaryFilePath)
waitsFor "directory view contens to refresh", ->
treeView.root.entries.find('.entry').length == entriesCountBefore