From 6be10aad0d0055395210fbb2d23a58b15779aa13 Mon Sep 17 00:00:00 2001 From: Nicklas Gummesson Date: Sat, 24 Jun 2017 10:56:16 -0700 Subject: [PATCH] Apply prettier --- .eslintrc.js | 4 +- CHANGELOG.md | 2 + lib/action-creators.js | 189 ++++--- lib/atom-rxjs-observables.js | 52 +- lib/columns.js | 103 ++-- lib/columns/file-icon-column.js | 44 +- lib/columns/stats-date-column.js | 41 +- lib/columns/summary-column.js | 77 +-- lib/default-config.js | 75 +-- lib/disposables.js | 14 +- lib/epics/active-pane-item.js | 25 +- lib/epics/at-copy-match-to-clipboard.js | 21 +- lib/epics/config-changes.js | 102 ++-- lib/epics/file-reads.js | 158 +++--- lib/epics/file-writes.js | 44 +- lib/epics/hidden-columns.js | 88 +-- lib/epics/index.js | 32 +- lib/epics/path-watcher.js | 127 +++-- lib/epics/preview-note.js | 213 ++++--- lib/fields/parsed-path-field.js | 50 +- lib/fields/stats-date-field.js | 18 +- lib/file-readers.js | 51 +- lib/file-readers/content-file-reader.js | 11 +- lib/file-readers/file-icons-reader.js | 24 +- lib/file-readers/stats-file-reader.js | 9 +- lib/file-writers.js | 31 +- lib/get-valid-dir-from-path.js | 22 +- lib/initial-scan-task.js | 36 +- lib/log-error.js | 4 +- lib/main.js | 156 +++--- lib/note-fields.js | 32 +- lib/notes-cache.js | 85 +-- lib/notes-file-filter.js | 22 +- lib/preview-element.js | 53 +- lib/react/app.js | 40 +- lib/react/cell.js | 31 +- lib/react/edit-cell-str.js | 76 +-- lib/react/loading-progress.js | 39 +- lib/react/main.js | 174 +++--- lib/react/resize-handle.js | 34 +- lib/react/scrollable-list.js | 124 +++-- lib/react/search.js | 105 ++-- lib/react/table-column.js | 43 +- lib/reducers/column-headers.js | 21 +- lib/reducers/edit-cell-name.js | 21 +- lib/reducers/index.js | 59 +- lib/reducers/initial-scan.js | 19 +- lib/reducers/list-height.js | 8 +- lib/reducers/notes.js | 103 ++-- lib/reducers/query-original.js | 10 +- lib/reducers/row-height.js | 8 +- lib/reducers/scroll-top.js | 24 +- lib/reducers/selected-note.js | 61 +- lib/reducers/sifter-result.js | 54 +- lib/reselectors/pagination.js | 14 +- lib/reselectors/visible-rows.js | 63 ++- ...t-session-for-new-config-to-take-effect.js | 91 ++- lib/search-match.js | 16 +- lib/service-consumers/defaults.js | 76 +-- lib/service-consumers/nv-tags.js | 160 +++--- lib/service-consumers/rename-note.js | 50 +- lib/service.js | 55 +- lib/session.js | 94 ++-- lib/toggle-atom-window.js | 43 +- lib/toggle-panel.js | 33 +- package.json | 20 +- spec/_async-spec-helpers.js | 70 +-- spec/_fix-spec.js | 11 +- spec/atom-rxjs-observables-spec.js | 106 ++-- spec/columns-spec.js | 92 +-- spec/columns/file-icon-column-spec.js | 88 +-- spec/columns/stats-date-column-spec.js | 92 +-- spec/columns/summary-column-spec.js | 180 +++--- spec/dispatch-keydown-event.js | 12 +- spec/epics/active-pane-item-spec.js | 148 ++--- spec/epics/config-changes-spec.js | 219 ++++---- spec/epics/file-reads-spec.js | 352 ++++++------ spec/epics/file-writes-spec.js | 151 ++--- spec/epics/hidden-columns-spec.js | 130 +++-- spec/epics/path-watcher-spec.js | 244 ++++---- spec/epics/preview-note-spec.js | 326 +++++------ spec/fields/parsed-path-field-spec.js | 47 +- spec/fields/stats-date-field-spec.js | 51 +- spec/get-valid-dir-from-path-spec.js | 76 +-- spec/initial-scan-task-spec.js | 102 ++-- spec/main-spec.js | 146 ++--- spec/note-fields-spec.js | 77 +-- spec/notes-cache-spec.js | 68 +-- spec/notes-file-filter-spec.js | 104 ++-- spec/preview-element-spec.js | 88 +-- spec/react/edit-cell-str-spec.js | 122 ++-- spec/reducers/column-headers-spec.js | 77 +-- spec/reducers/initial-scan-spec.js | 126 +++-- spec/reducers/list-height-spec.js | 96 ++-- spec/reducers/notes-spec.js | 297 +++++----- spec/reducers/row-height-spec.js | 76 +-- spec/reducers/scroll-top-spec.js | 324 ++++++----- spec/reducers/selected-note-spec.js | 522 ++++++++++-------- spec/reducers/sifter-result-spec.js | 389 ++++++------- spec/reselectors/pagination-spec.js | 60 +- spec/reselectors/visible-rows-spec.js | 376 +++++++------ spec/service-consumers/nv-tags-spec.js | 291 +++++----- spec/service-consumers/rename-note-spec.js | 128 +++-- 103 files changed, 5182 insertions(+), 4466 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index ebc2664..2840ff8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,10 +1,12 @@ module.exports = { - "extends": ["standard", "standard-jsx"], + "extends": ["prettier"], "parser": "babel-eslint", "plugins": [ + "prettier", "flowtype" ], "rules": { + "prettier/prettier": "error", "flowtype/define-flow-type": 1, "flowtype/use-flow-type": 1 }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 0c60650..7d3b603 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/) and [keepachangelog.com](http://keepachangelog.com/). ## [unreleased] +#### Changed +- Using [prettier](https://github.com/prettier/prettier) for automatic code formatting ## [0.15.0] - 2017-06-20 #### Fixed diff --git a/lib/action-creators.js b/lib/action-creators.js index c88d5ff..759b8d4 100644 --- a/lib/action-creators.js +++ b/lib/action-creators.js @@ -1,194 +1,201 @@ /* @flow */ -export const CHANGED_ACTIVE_PANE_ITEM = 'CHANGED_ACTIVE_PANE_ITEM' -export function changedActivePaneItem (path: ?string): ChangedActivePaneItem { +export const CHANGED_ACTIVE_PANE_ITEM = "CHANGED_ACTIVE_PANE_ITEM"; +export function changedActivePaneItem(path: ?string): ChangedActivePaneItem { return { type: CHANGED_ACTIVE_PANE_ITEM, path - } + }; } -export const CHANGED_HIDDEN_COLUMNS = 'CHANGED_HIDDEN_COLUMNS' -export function changeHiddenColumns (hiddenColumns: string[]): ChangedHiddenColumns { +export const CHANGED_HIDDEN_COLUMNS = "CHANGED_HIDDEN_COLUMNS"; +export function changeHiddenColumns( + hiddenColumns: string[] +): ChangedHiddenColumns { return { type: CHANGED_HIDDEN_COLUMNS, hiddenColumns - } + }; } -export const CHANGED_LIST_HEIGHT = 'CHANGED_LIST_HEIGHT' -export function changeListHeight (listHeight: number): ChangedListHeight { +export const CHANGED_LIST_HEIGHT = "CHANGED_LIST_HEIGHT"; +export function changeListHeight(listHeight: number): ChangedListHeight { return { type: CHANGED_LIST_HEIGHT, listHeight - } + }; } -export const CHANGED_ROW_HEIGHT = 'CHANGED_ROW_HEIGHT' -export function changeRowHeight (rowHeight: number): ChangedRowHeight { +export const CHANGED_ROW_HEIGHT = "CHANGED_ROW_HEIGHT"; +export function changeRowHeight(rowHeight: number): ChangedRowHeight { return { type: CHANGED_ROW_HEIGHT, rowHeight - } + }; } -export const CHANGED_SORT_DIRECTION = 'CHANGED_SORT_DIRECTION' -export function changeSortDirection (sortDirection: SortDirection): ChangedSortDirection { +export const CHANGED_SORT_DIRECTION = "CHANGED_SORT_DIRECTION"; +export function changeSortDirection( + sortDirection: SortDirection +): ChangedSortDirection { return { type: CHANGED_SORT_DIRECTION, sortDirection - } + }; } -export const CHANGED_SORT_FIELD = 'CHANGED_SORT_FIELD' -export function changeSortField (sortField: string): ChangedSortField { +export const CHANGED_SORT_FIELD = "CHANGED_SORT_FIELD"; +export function changeSortField(sortField: string): ChangedSortField { return { type: CHANGED_SORT_FIELD, sortField - } + }; } -export const CLICK_ROW = 'CLICK_ROW' -export function clickRow (filename: string): ClickRow { +export const CLICK_ROW = "CLICK_ROW"; +export function clickRow(filename: string): ClickRow { return { type: CLICK_ROW, filename - } + }; } -export const DISPOSE = 'DISPOSE' -export function dispose (): Dispose { - return {type: DISPOSE} +export const DISPOSE = "DISPOSE"; +export function dispose(): Dispose { + return { type: DISPOSE }; } -export const EDIT_CELL = 'EDIT_CELL' -export const EDIT_CELL_ABORT = 'EDIT_CELL_ABORT' -export const EDIT_CELL_SAVE = 'EDIT_CELL_SAVE' -export function editCell (name: string): EditCell { +export const EDIT_CELL = "EDIT_CELL"; +export const EDIT_CELL_ABORT = "EDIT_CELL_ABORT"; +export const EDIT_CELL_SAVE = "EDIT_CELL_SAVE"; +export function editCell(name: string): EditCell { return { type: EDIT_CELL, name - } + }; } -export function editCellAbort (): EditCellAbort { - return {type: EDIT_CELL_ABORT} +export function editCellAbort(): EditCellAbort { + return { type: EDIT_CELL_ABORT }; } -export function editCellSave (editCellName: string, value: string): EditCellSave { +export function editCellSave( + editCellName: string, + value: string +): EditCellSave { return { type: EDIT_CELL_SAVE, editCellName, value - } + }; } -export const FILE_ADDED = 'FILE_ADDED' -export const FILE_CHANGED = 'FILE_CHANGED' -export const FILE_DELETED = 'FILE_DELETED' -export const FILE_READ = 'FILE_READ' -export function fileAdded (rawFile: RawFile): FileAdded { +export const FILE_ADDED = "FILE_ADDED"; +export const FILE_CHANGED = "FILE_CHANGED"; +export const FILE_DELETED = "FILE_DELETED"; +export const FILE_READ = "FILE_READ"; +export function fileAdded(rawFile: RawFile): FileAdded { return { type: FILE_ADDED, rawFile - } + }; } -export function fileChanged (rawFile: RawFile): FileChanged { +export function fileChanged(rawFile: RawFile): FileChanged { return { type: FILE_CHANGED, rawFile - } + }; } -export function fileDeleted (filename: string): FileDeleted { +export function fileDeleted(filename: string): FileDeleted { return { type: FILE_DELETED, filename - } + }; } -export function fileRead (result: FileReadResult): FileRead { +export function fileRead(result: FileReadResult): FileRead { return { type: FILE_READ, ...result - } + }; } -export const INITIAL_SCAN_DONE = 'INITIAL_SCAN_DONE' -export function initialScanDone (): InitialScanDone { - return {type: INITIAL_SCAN_DONE} +export const INITIAL_SCAN_DONE = "INITIAL_SCAN_DONE"; +export function initialScanDone(): InitialScanDone { + return { type: INITIAL_SCAN_DONE }; } -export const INITIAL_SCAN_RAW_FILES_READ = 'INITIAL_SCAN_RAW_FILES_READ' -export function initialScanRawFilesRead (): InitialScanRawFilesRead { - return {type: INITIAL_SCAN_RAW_FILES_READ} +export const INITIAL_SCAN_RAW_FILES_READ = "INITIAL_SCAN_RAW_FILES_READ"; +export function initialScanRawFilesRead(): InitialScanRawFilesRead { + return { type: INITIAL_SCAN_RAW_FILES_READ }; } -export const DOWN = 40 -export const ENTER = 13 -export const ESC = 27 -export const UP = 38 -export function keyPress (event: KeyPressEvent): Function { +export const DOWN = 40; +export const ENTER = 13; +export const ESC = 27; +export const UP = 38; +export function keyPress(event: KeyPressEvent): Function { return (dispatch: Function) => { switch (event.keyCode) { case ENTER: - dispatch(openNote()) - break + dispatch(openNote()); + break; case ESC: - dispatch(resetSearch()) - break + dispatch(resetSearch()); + break; case DOWN: - event.preventDefault() - dispatch(selectNext()) - break + event.preventDefault(); + dispatch(selectNext()); + break; case UP: - event.preventDefault() - dispatch(selectPrev()) - break + event.preventDefault(); + dispatch(selectPrev()); + break; } - } + }; } -export const OPEN_NOTE = 'OPEN_NOTE' -export function openNote (): OpenNote { - return {type: OPEN_NOTE} +export const OPEN_NOTE = "OPEN_NOTE"; +export function openNote(): OpenNote { + return { type: OPEN_NOTE }; } -export const RESET_SEARCH = 'RESET_SEARCH' -export function resetSearch (): ResetSearch { - return {type: RESET_SEARCH} +export const RESET_SEARCH = "RESET_SEARCH"; +export function resetSearch(): ResetSearch { + return { type: RESET_SEARCH }; } -export const RESIZED_LIST = 'RESIZED_LIST' -export function resizeList (listHeight: number): ResizedList { +export const RESIZED_LIST = "RESIZED_LIST"; +export function resizeList(listHeight: number): ResizedList { return { type: RESIZED_LIST, listHeight - } + }; } -export const SCROLLED = 'SCROLLED' -export function scroll (scrollTop: number): Scrolled { +export const SCROLLED = "SCROLLED"; +export function scroll(scrollTop: number): Scrolled { return { type: SCROLLED, scrollTop - } + }; } -export const SEARCH = 'SEARCH' -export function search (query: string): Search { +export const SEARCH = "SEARCH"; +export function search(query: string): Search { return { type: SEARCH, query - } + }; } -export const SELECT_NEXT = 'SELECT_NEXT' -export function selectNext (): SelectNext { - return {type: SELECT_NEXT} +export const SELECT_NEXT = "SELECT_NEXT"; +export function selectNext(): SelectNext { + return { type: SELECT_NEXT }; } -export const SELECT_PREV = 'SELECT_PREV' -export function selectPrev (): SelectPrev { - return {type: SELECT_PREV} +export const SELECT_PREV = "SELECT_PREV"; +export function selectPrev(): SelectPrev { + return { type: SELECT_PREV }; } -export const START_INITIAL_SCAN = 'START_INITIAL_SCAN' -export function startInitialScan (): StartInitialScan { - return {type: START_INITIAL_SCAN} +export const START_INITIAL_SCAN = "START_INITIAL_SCAN"; +export function startInitialScan(): StartInitialScan { + return { type: START_INITIAL_SCAN }; } diff --git a/lib/atom-rxjs-observables.js b/lib/atom-rxjs-observables.js index 6f36c9d..8b5b5e4 100644 --- a/lib/atom-rxjs-observables.js +++ b/lib/atom-rxjs-observables.js @@ -1,35 +1,41 @@ /* @flow */ -import {Observable} from 'rxjs' +import { Observable } from "rxjs"; -export function observeConfig (key: string) { - if (typeof key !== 'string') throw new Error(`key must be a string, given key was ${JSON.stringify(key)}`) - if (key.trim().length === 0) throw new Error(`key must be a non-empty string`) +export function observeConfig(key: string) { + if (typeof key !== "string") + throw new Error( + `key must be a string, given key was ${JSON.stringify(key)}` + ); + if (key.trim().length === 0) + throw new Error(`key must be a non-empty string`); - return Observable.create(function subscribe (observer) { + return Observable.create(function subscribe(observer) { const disposable = atom.config.observe(key, (value: any) => { - observer.next(value) - }) + observer.next(value); + }); - return function unsubscribe () { - disposable.dispose() - observer.complete() - } - }) + return function unsubscribe() { + disposable.dispose(); + observer.complete(); + }; + }); } -export function observe (obj: Object, method: string) { - if (method.trim().length === 0) throw new Error(`method must be a non-empty string`) - if (typeof obj[method] !== 'function') throw new Error(`method must be a function on the given object`) +export function observe(obj: Object, method: string) { + if (method.trim().length === 0) + throw new Error(`method must be a non-empty string`); + if (typeof obj[method] !== "function") + throw new Error(`method must be a function on the given object`); - return Observable.create(function subscribe (observer) { + return Observable.create(function subscribe(observer) { const disposable = obj[method]((value: any) => { - observer.next(value) - }) + observer.next(value); + }); - return function unsubscribe () { - disposable.dispose() - observer.complete() - } - }) + return function unsubscribe() { + disposable.dispose(); + observer.complete(); + }; + }); } diff --git a/lib/columns.js b/lib/columns.js index ef98f46..d59180b 100644 --- a/lib/columns.js +++ b/lib/columns.js @@ -1,88 +1,85 @@ /* @flow */ -import logError from './log-error' +import logError from "./log-error"; -const privates = new WeakMap() +const privates = new WeakMap(); -export function name (column: {title: string}) { - return column.title.toLowerCase().replace(/\s/, '-') +export function name(column: { title: string }) { + return column.title.toLowerCase().replace(/\s/, "-"); } export default class Columns { - constructor () { - privates.set(this, []) + constructor() { + privates.set(this, []); } - add (column: Column) { - if (typeof column !== 'object') return logError('column object is required', column) - if (typeof column.sortField !== 'string') return logError('column.sortField string is required', column) - if (typeof column.title !== 'string') return logError('column.title string is required', column) - if (typeof column.cellContent !== 'function') return logError('column.cellContent function is required, was', column) - - const columns = privates.get(this) + add(column: Column) { + if (typeof column !== "object") + return logError("column object is required", column); + if (typeof column.sortField !== "string") + return logError("column.sortField string is required", column); + if (typeof column.title !== "string") + return logError("column.title string is required", column); + if (typeof column.cellContent !== "function") + return logError("column.cellContent function is required, was", column); + + const columns = privates.get(this); if (columns) { - if (typeof column.position === 'number') { - const start = parseInt(column.position) || 0 - columns.splice(start, 0, column) + if (typeof column.position === "number") { + const start = parseInt(column.position) || 0; + columns.splice(start, 0, column); } else { - columns.push(column) + columns.push(column); } - updateConfigSchema(columns) + updateConfigSchema(columns); } } - filter (predicate: (column: Column) => boolean): Column[] { - const columns = privates.get(this) - return columns - ? columns.filter(predicate) - : [] + filter(predicate: (column: Column) => boolean): Column[] { + const columns = privates.get(this); + return columns ? columns.filter(predicate) : []; } - map (mapper: (column: Column) => T): Array { - const columns = privates.get(this) - return columns - ? columns.map(mapper) - : [] + map(mapper: (column: Column) => T): Array { + const columns = privates.get(this); + return columns ? columns.map(mapper) : []; } - some (predicate: (column: Column) => boolean): boolean { - const columns = privates.get(this) - return columns - ? columns.some(predicate) - : false + some(predicate: (column: Column) => boolean): boolean { + const columns = privates.get(this); + return columns ? columns.some(predicate) : false; } - dispose () { - privates.delete(this) + dispose() { + privates.delete(this); } } -function updateConfigSchema (columns: Column[]) { - const schema = atom.config.getSchema('textual-velocity.sortField') - schema.default = columns[0].sortField +function updateConfigSchema(columns: Column[]) { + const schema = atom.config.getSchema("textual-velocity.sortField"); + schema.default = columns[0].sortField; - const currentVal = atom.config.get('textual-velocity.sortField') - let hasCurrentValInEnum = false + const currentVal = atom.config.get("textual-velocity.sortField"); + let hasCurrentValInEnum = false; - schema.enum = columns - .map(column => { - if (column.sortField === currentVal) { - hasCurrentValInEnum = true - } - return { - value: column.sortField, - description: column.title - } - }) + schema.enum = columns.map(column => { + if (column.sortField === currentVal) { + hasCurrentValInEnum = true; + } + return { + value: column.sortField, + description: column.title + }; + }); if (!hasCurrentValInEnum) { schema.enum.push({ value: currentVal, description: currentVal - }) + }); } - atom.config.setSchema('textual-velocity.sortField', schema) - atom.config.set('textual-velocity.sortField', currentVal) + atom.config.setSchema("textual-velocity.sortField", schema); + atom.config.set("textual-velocity.sortField", currentVal); } diff --git a/lib/columns/file-icon-column.js b/lib/columns/file-icon-column.js index 91cb75f..645caa0 100644 --- a/lib/columns/file-icon-column.js +++ b/lib/columns/file-icon-column.js @@ -1,46 +1,46 @@ /* @flow */ -import fs from 'fs-plus' +import fs from "fs-plus"; export default class FileIconColumn { - description: string - sortField: string - title: string - width: number + description: string; + sortField: string; + title: string; + width: number; - constructor (params: {sortField: string}) { - this.description = 'File extension' - this.sortField = params.sortField - this.title = 'File type' - this.width = 4 + constructor(params: { sortField: string }) { + this.description = "File extension"; + this.sortField = params.sortField; + this.title = "File type"; + this.width = 4; } - cellContent (params: CellContentParams): CellContent { - const {note} = params + cellContent(params: CellContentParams): CellContent { + const { note } = params; return { attrs: { className: note.fileIcons || this._defaultFileIcons(params.path, note), - 'data-name': note.name + note.ext + "data-name": note.name + note.ext } - } + }; } - _defaultFileIcons (path: string, note: Note) { + _defaultFileIcons(path: string, note: Note) { // from https://github.com/atom/tree-view/blob/9dcc89fc0c8505528f393b5ebdd93616a8adbd68/lib/default-file-icons.coffee if (fs.isSymbolicLinkSync(path)) { - return 'icon icon-file-symlink-file' + return "icon icon-file-symlink-file"; } else if (fs.isReadmePath(path)) { - return 'icon icon-book' + return "icon icon-book"; } else if (fs.isCompressedExtension(note.ext)) { - return 'icon icon-file-zip' + return "icon icon-file-zip"; } else if (fs.isImageExtension(note.ext)) { - return 'icon icon-file-media' + return "icon icon-file-media"; } else if (fs.isPdfExtension(note.ext)) { - return 'icon icon-file-pdf' + return "icon icon-file-pdf"; } else if (fs.isBinaryExtension(note.ext)) { - return 'icon icon-file-binary' + return "icon icon-file-binary"; } else { - return 'icon icon-file-text' + return "icon icon-file-text"; } } } diff --git a/lib/columns/stats-date-column.js b/lib/columns/stats-date-column.js index 0bd7f22..2616982 100644 --- a/lib/columns/stats-date-column.js +++ b/lib/columns/stats-date-column.js @@ -1,27 +1,32 @@ /* @flow */ -import moment from 'moment' +import moment from "moment"; export default class StatsDateColumn { - className: string|void - description: string - notePropName: string - sortField: string - title: string - width: number + className: string | void; + description: string; + notePropName: string; + sortField: string; + title: string; + width: number; - constructor (params: {sortField: string, title: string, description: string, notePropName: string}) { - this.className = 'stats' - this.description = params.description - this.notePropName = params.notePropName - this.sortField = params.sortField - this.title = params.title - this.width = 14 + constructor(params: { + sortField: string, + title: string, + description: string, + notePropName: string + }) { + this.className = "stats"; + this.description = params.description; + this.notePropName = params.notePropName; + this.sortField = params.sortField; + this.title = params.title; + this.width = 14; } - cellContent (params: CellContentParams): CellContent { - const {note} = params - const date = note.stats && note.stats[this.notePropName] - return (date && moment(date.getTime()).fromNow()) || '' + cellContent(params: CellContentParams): CellContent { + const { note } = params; + const date = note.stats && note.stats[this.notePropName]; + return (date && moment(date.getTime()).fromNow()) || ""; } } diff --git a/lib/columns/summary-column.js b/lib/columns/summary-column.js index 56281df..81c3e4b 100644 --- a/lib/columns/summary-column.js +++ b/lib/columns/summary-column.js @@ -1,58 +1,65 @@ /* @flow */ -const MAX_PREVIEW_LENGTH = 400 // characters -const HIGHLIGHT_PREVIEW_PADDING_LENGTH = 20 // characters +const MAX_PREVIEW_LENGTH = 400; // characters +const HIGHLIGHT_PREVIEW_PADDING_LENGTH = 20; // characters export default class SummaryColumn { - className: string|void - description: string - editCellName: string | void - editCellStr: void | (note: Note) => string - sortField: string - title: string - width: number - - constructor (params: {editCellName: string, sortField: string}) { - this.className = 'summary' - this.description = 'File name and content preview' - this.editCellName = params.editCellName - this.sortField = params.sortField - this.title = 'Summary' - this.width = 48 + className: string | void; + description: string; + editCellName: string | void; + editCellStr: void | ((note: Note) => string); + sortField: string; + title: string; + width: number; + + constructor(params: { editCellName: string, sortField: string }) { + this.className = "summary"; + this.description = "File name and content preview"; + this.editCellName = params.editCellName; + this.sortField = params.sortField; + this.title = "Summary"; + this.width = 48; } - editCellStr (note: Note): string { - return note.name + note.ext + editCellStr(note: Note): string { + return note.name + note.ext; } - cellContent (params: CellContentParams): CellContent { - const {note, searchMatch} = params + cellContent(params: CellContentParams): CellContent { + const { note, searchMatch } = params; return [ (searchMatch && searchMatch.content(note.name)) || note.name, - {content: note.ext, attrs: {className: 'text-subtle'}}, - ' - ', + { content: note.ext, attrs: { className: "text-subtle" } }, + " - ", this._preview(note, searchMatch) - ] + ]; } - _preview (note: Note, searchMatch?: SearchMatch): Object { - const str = note.content - let content + _preview(note: Note, searchMatch?: SearchMatch): Object { + const str = note.content; + let content; if (str) { - content = searchMatch && searchMatch.content(str) + content = searchMatch && searchMatch.content(str); if (content) { - const highlightStart = str.indexOf(content[1].content) - const highlightEnd = content[1].content.length - const start = Math.max(0, highlightStart - HIGHLIGHT_PREVIEW_PADDING_LENGTH) - content[0] = (start > 0 ? '…' : '') + content[0].slice(start, highlightStart) - content[2] = content[2].slice(0, Math.max(0, MAX_PREVIEW_LENGTH - (highlightStart + highlightEnd))) + const highlightStart = str.indexOf(content[1].content); + const highlightEnd = content[1].content.length; + const start = Math.max( + 0, + highlightStart - HIGHLIGHT_PREVIEW_PADDING_LENGTH + ); + content[0] = + (start > 0 ? "…" : "") + content[0].slice(start, highlightStart); + content[2] = content[2].slice( + 0, + Math.max(0, MAX_PREVIEW_LENGTH - (highlightStart + highlightEnd)) + ); } } return { - attrs: {className: 'text-subtle'}, + attrs: { className: "text-subtle" }, content: content || (str && str.slice(0, MAX_PREVIEW_LENGTH)) - } + }; } } diff --git a/lib/default-config.js b/lib/default-config.js index 4f47a8f..fa348d2 100644 --- a/lib/default-config.js +++ b/lib/default-config.js @@ -1,82 +1,85 @@ /* @flow */ -const restartExplanation = '_Changing this setting requires restarting the session._' -const concurrentValueConsequenceExplanation = 'A higher value might (but no guarantee) make the processing a little faster, but makes the Atom GUI less responsive.' +const restartExplanation = + "_Changing this setting requires restarting the session._"; +const concurrentValueConsequenceExplanation = + "A higher value might (but no guarantee) make the processing a little faster, but makes the Atom GUI less responsive."; const cfg = { path: { description: `${restartExplanation}
Path to folder where to find notes. Can be an absolute path or a relative path to \`~/.atom\` (defaults to \`~/.atom/notes\`)`, - type: 'string', - default: '' + type: "string", + default: "" }, ignoredNames: { - type: 'array', - default: ['Notes & Settings'], - items: {type: 'string'}, + type: "array", + default: ["Notes & Settings"], + items: { type: "string" }, description: `${restartExplanation}
List of [glob patterns](https://en.wikipedia.org/wiki/Glob_%28programming%29). Files matching these patterns will be ignored, in addition to the ignoredNames defined in core settings` }, excludeVcsIgnoredPaths: { - type: 'boolean', + type: "boolean", default: true, - title: 'Exclude VCS Ignored Paths', + title: "Exclude VCS Ignored Paths", description: `${restartExplanation}
Files ignored by the the notes path's VCS system will be ignored. For example, projects using Git have these paths defined in the .gitignore file.` }, sortField: { - default: 'name', - type: 'string' + default: "name", + type: "string" }, sortDirection: { - type: 'string', - default: 'desc', + type: "string", + default: "desc", enum: [ - {value: 'asc', description: 'Ascending order'}, - {value: 'desc', description: 'Descending order'} + { value: "asc", description: "Ascending order" }, + { value: "desc", description: "Descending order" } ] }, hiddenColumns: { - type: 'array', + type: "array", default: [], - items: {type: 'string'}, - description: 'These columns will not be visible. Right-click on header to see options, or use commands panel (`textual-velocity:toggle-column-name-of-column`)' + items: { type: "string" }, + description: + "These columns will not be visible. Right-click on header to see options, or use commands panel (`textual-velocity:toggle-column-name-of-column`)" }, defaultExt: { - title: 'Default file extension', - description: 'Will be used for new files, unless the text string contains a custom file extension already', - type: 'string', - default: '.md' + title: "Default file extension", + description: + "Will be used for new files, unless the text string contains a custom file extension already", + type: "string", + default: ".md" }, listHeight: { - description: 'Height of panel, can also be changed by dragging the bottom of panel', - type: 'number', + description: + "Height of panel, can also be changed by dragging the bottom of panel", + type: "number", default: 150, minimum: 0 }, rowHeight: { - description: 'Internal cached value, used to calculate pagination size', - type: 'number', + description: "Internal cached value, used to calculate pagination size", + type: "number", default: 20, minimum: 8, maximum: 80 }, concurrentFilesParses: { description: `${restartExplanation}
Defines how many files to parse concurrently when the initial dir scan is done.
${concurrentValueConsequenceExplanation}`, - type: 'number', + type: "number", default: 3, minimum: 0 }, concurrentFileReads: { description: `${restartExplanation}
Defines how many concurrent read operations to do per file.
${concurrentValueConsequenceExplanation}`, - type: 'number', + type: "number", default: 3, minimum: 0 } -} +}; -Object - .keys(cfg) - .forEach((key, i) => { - const setting: Object = cfg[key] - setting.order = i - }) +Object.keys(cfg).forEach((key, i) => { + const setting: Object = cfg[key]; + setting.order = i; +}); -export default cfg +export default cfg; diff --git a/lib/disposables.js b/lib/disposables.js index 85f2a2d..b07b7d9 100644 --- a/lib/disposables.js +++ b/lib/disposables.js @@ -1,6 +1,6 @@ /* @flow */ -import {Disposable, CompositeDisposable} from 'atom' +import { Disposable, CompositeDisposable } from "atom"; // Make it easier to define side-effects for BaconJS streams // @@ -11,16 +11,18 @@ import {Disposable, CompositeDisposable} from 'atom' // console.log('will be removed on disposable.dispose()') // })) export default class Disposables extends CompositeDisposable { - add (...values: Array) { + add(...values: Array) { for (let val of values) { if (!Disposable.isDisposable(val)) { - if (typeof val === 'function') { - val = new Disposable(val) + if (typeof val === "function") { + val = new Disposable(val); } else { - console.error(`val is of type ${typeof val}, was expected to be disposable or a function`) + console.error( + `val is of type ${typeof val}, was expected to be disposable or a function` + ); } } - super.add(val) + super.add(val); } } } diff --git a/lib/epics/active-pane-item.js b/lib/epics/active-pane-item.js index f2ac9cb..44d130c 100644 --- a/lib/epics/active-pane-item.js +++ b/lib/epics/active-pane-item.js @@ -1,23 +1,24 @@ /* @flow */ -import {observe} from '../atom-rxjs-observables' -import * as A from '../action-creators' +import { observe } from "../atom-rxjs-observables"; +import * as A from "../action-creators"; -export default function activePaneItemEpic (action$: rxjs$Observable, store: Store) { - return observe(atom.workspace, 'onDidStopChangingActivePaneItem') +export default function activePaneItemEpic( + action$: rxjs$Observable, + store: Store +) { + return observe(atom.workspace, "onDidStopChangingActivePaneItem") .withLatestFrom(action$.map(() => Date.now())) .map(([paneItem, lastActionTimestamp]) => { - if ((Date.now() - lastActionTimestamp) > 100) { - let path = null - if (paneItem && typeof paneItem.getPath === 'function') { - path = paneItem.getPath() + if (Date.now() - lastActionTimestamp > 100) { + let path = null; + if (paneItem && typeof paneItem.getPath === "function") { + path = paneItem.getPath(); } - return A.changedActivePaneItem(path) + return A.changedActivePaneItem(path); } }) .filter(action => !!action) - .takeUntil( - action$.filter(action => action.type === A.DISPOSE) - ) + .takeUntil(action$.filter(action => action.type === A.DISPOSE)); } diff --git a/lib/epics/at-copy-match-to-clipboard.js b/lib/epics/at-copy-match-to-clipboard.js index 31bef80..a7f8df1 100644 --- a/lib/epics/at-copy-match-to-clipboard.js +++ b/lib/epics/at-copy-match-to-clipboard.js @@ -1,23 +1,26 @@ /* @flow */ -export default function atCopyMatchToClipboardEpic (action$: rxjs$Observable, store: Store) { +export default function atCopyMatchToClipboardEpic( + action$: rxjs$Observable, + store: Store +) { return action$ .debounceTime(25) // ms .filter(() => { - const state: State = store.getState() + const state: State = store.getState(); if (state.selectedNote) { - const filename = state.selectedNote.filename - const note = state.notes[filename] + const filename = state.selectedNote.filename; + const note = state.notes[filename]; - if (note && typeof note.content === 'string') { - const match = note.content.match(/@copy\(([\S\s]*)\)/) + if (note && typeof note.content === "string") { + const match = note.content.match(/@copy\(([\S\s]*)\)/); if (match && match[1]) { - atom.clipboard.write(match[1]) + atom.clipboard.write(match[1]); } } } - return false - }) + return false; + }); } diff --git a/lib/epics/config-changes.js b/lib/epics/config-changes.js index 03f917f..f190575 100644 --- a/lib/epics/config-changes.js +++ b/lib/epics/config-changes.js @@ -1,50 +1,58 @@ /* @flow */ -import {Observable} from 'rxjs' -import {observeConfig} from '../atom-rxjs-observables' -import * as A from '../action-creators' - -export default function configChangesEpic (action$: rxjs$Observable, store: Store) { - return Observable - .merge( - observeConfig('textual-velocity.listHeight').map(A.changeListHeight).distinctUntilChanged(), - observeConfig('textual-velocity.rowHeight').map(A.changeRowHeight).distinctUntilChanged(), - observeConfig('textual-velocity.sortDirection').map(A.changeSortDirection).distinctUntilChanged(), - observeConfig('textual-velocity.sortField').map(A.changeSortField).distinctUntilChanged(), - - observeConfig('textual-velocity.editCellName') - .skip(1) // i.e. skip any value from previous session - .filter(editCellName => typeof editCellName === 'string') - .map(A.editCell) - .do(editCellName => { - // reset value immediately, will be excluded by filter check above - atom.config.set('textual-velocity.editCellName', null) - }), - - action$ // include side-effects stream in merge so it's unsubscribed upon dispose, too - .debounceTime(200) - .filter(action => { - switch (action.type) { - case A.RESIZED_LIST: - atom.config.set('textual-velocity.listHeight', action.listHeight) - break - - case A.CHANGED_ROW_HEIGHT: - atom.config.set('textual-velocity.rowHeight', action.rowHeight) - break - - case A.CHANGED_SORT_DIRECTION: - atom.config.set('textual-velocity.sortDirection', action.sortDirection) - break - - case A.CHANGED_SORT_FIELD: - atom.config.set('textual-velocity.sortField', action.sortField) - } - - return false // to avoid creating an infinite loop (see https://redux-observable.js.org/docs/basics/Epics.html) - }) - ) - .takeUntil( - action$.filter(action => action.type === A.DISPOSE) - ) +import { Observable } from "rxjs"; +import { observeConfig } from "../atom-rxjs-observables"; +import * as A from "../action-creators"; + +export default function configChangesEpic( + action$: rxjs$Observable, + store: Store +) { + return Observable.merge( + observeConfig("textual-velocity.listHeight") + .map(A.changeListHeight) + .distinctUntilChanged(), + observeConfig("textual-velocity.rowHeight") + .map(A.changeRowHeight) + .distinctUntilChanged(), + observeConfig("textual-velocity.sortDirection") + .map(A.changeSortDirection) + .distinctUntilChanged(), + observeConfig("textual-velocity.sortField") + .map(A.changeSortField) + .distinctUntilChanged(), + observeConfig("textual-velocity.editCellName") + .skip(1) // i.e. skip any value from previous session + .filter(editCellName => typeof editCellName === "string") + .map(A.editCell) + .do(editCellName => { + // reset value immediately, will be excluded by filter check above + atom.config.set("textual-velocity.editCellName", null); + }), + action$ // include side-effects stream in merge so it's unsubscribed upon dispose, too + .debounceTime(200) + .filter(action => { + switch (action.type) { + case A.RESIZED_LIST: + atom.config.set("textual-velocity.listHeight", action.listHeight); + break; + + case A.CHANGED_ROW_HEIGHT: + atom.config.set("textual-velocity.rowHeight", action.rowHeight); + break; + + case A.CHANGED_SORT_DIRECTION: + atom.config.set( + "textual-velocity.sortDirection", + action.sortDirection + ); + break; + + case A.CHANGED_SORT_FIELD: + atom.config.set("textual-velocity.sortField", action.sortField); + } + + return false; // to avoid creating an infinite loop (see https://redux-observable.js.org/docs/basics/Epics.html) + }) + ).takeUntil(action$.filter(action => action.type === A.DISPOSE)); } diff --git a/lib/epics/file-reads.js b/lib/epics/file-reads.js index 582e8c6..cc8b560 100644 --- a/lib/epics/file-reads.js +++ b/lib/epics/file-reads.js @@ -1,88 +1,96 @@ /* @flow */ -import Path from 'path' -import {Observable} from 'rxjs' -import * as A from '../action-creators' +import Path from "path"; +import { Observable } from "rxjs"; +import * as A from "../action-creators"; -export default function makeFileReadsEpic (fileReaders: FileReaders) { - return function fileReadsEpic (action$: rxjs$Observable, store: Store) { - return Observable - .merge( - action$ - .filter(action => action.type === A.INITIAL_SCAN_DONE) - .mergeMap(() => { - const state: State = store.getState() +export default function makeFileReadsEpic(fileReaders: FileReaders) { + return function fileReadsEpic( + action$: rxjs$Observable, + store: Store + ) { + return Observable.merge( + action$ + .filter(action => action.type === A.INITIAL_SCAN_DONE) + .mergeMap(() => { + const state: State = store.getState(); - return Observable - .from(state.initialScan.rawFiles) - .mergeMap(rawFile => { - const state: State = store.getState() + return Observable.from( + state.initialScan.rawFiles + ).mergeMap(rawFile => { + const state: State = store.getState(); - let fileReadersArray = fileReaders.map(fileReader => fileReader) - const note = state.notes[rawFile.filename] - if (note && note.stats.mtime.getTime() === rawFile.stats.mtime.getTime()) { - fileReadersArray = fileReadersArray.filter(fileReader => note[fileReader.notePropName] === undefined) - if (fileReadersArray.length === 0) return Observable.empty() // e.g. a cached note that have all read values already - } - - return makeReadFileObservable(fileReadersArray, rawFile, state.dir) - }, atom.config.get('textual-velocity.concurrentFilesParses')) - }), - - action$ - .map(action => { - if (action.type === A.INITIAL_SCAN_DONE) { - return A.initialScanRawFilesRead() + let fileReadersArray = fileReaders.map(fileReader => fileReader); + const note = state.notes[rawFile.filename]; + if ( + note && + note.stats.mtime.getTime() === rawFile.stats.mtime.getTime() + ) { + fileReadersArray = fileReadersArray.filter( + fileReader => note[fileReader.notePropName] === undefined + ); + if (fileReadersArray.length === 0) return Observable.empty(); // e.g. a cached note that have all read values already } - }), - action$ - .filter(action => { - switch (action.type) { - case A.FILE_ADDED: - case A.FILE_CHANGED: - const state: State = store.getState() - return state.initialScan.done - default: - return false - } - }) - .mergeMap((action: any) => { - const state: State = store.getState() - const fileReadersArray = fileReaders.map(fileReader => fileReader) - return makeReadFileObservable(fileReadersArray, action.rawFile, state.dir) - }) - ) + return makeReadFileObservable(fileReadersArray, rawFile, state.dir); + }, atom.config.get("textual-velocity.concurrentFilesParses")); + }), + action$.map(action => { + if (action.type === A.INITIAL_SCAN_DONE) { + return A.initialScanRawFilesRead(); + } + }), + action$ + .filter(action => { + switch (action.type) { + case A.FILE_ADDED: + case A.FILE_CHANGED: + const state: State = store.getState(); + return state.initialScan.done; + default: + return false; + } + }) + .mergeMap((action: any) => { + const state: State = store.getState(); + const fileReadersArray = fileReaders.map(fileReader => fileReader); + return makeReadFileObservable( + fileReadersArray, + action.rawFile, + state.dir + ); + }) + ) .filter(action => !!action) - .takeUntil( - action$.filter(action => action.type === A.DISPOSE) - ) - } + .takeUntil(action$.filter(action => action.type === A.DISPOSE)); + }; } -function makeReadFileObservable (fileReadersArray: Array, rawFile: RawFile, dir: string) { - return Observable - .from(fileReadersArray) - .mergeMap(fileReader => { - const result: FileReadResult = { - filename: rawFile.filename, - notePropName: fileReader.notePropName, - value: null - } +function makeReadFileObservable( + fileReadersArray: Array, + rawFile: RawFile, + dir: string +) { + return Observable.from(fileReadersArray).mergeMap(fileReader => { + const result: FileReadResult = { + filename: rawFile.filename, + notePropName: fileReader.notePropName, + value: null + }; - const path = Path.join(dir, rawFile.filename) + const path = Path.join(dir, rawFile.filename); - const readAsObservable = Observable.bindNodeCallback(fileReader.read.bind(fileReader)) - return readAsObservable(path, rawFile.stats) - .map(readFileValue => { - result.value = readFileValue !== undefined - ? readFileValue - : null // make sure value cannot be undefined - return A.fileRead(result) - }) - .catch(err => { - console.warn('failed to read file:', {err, context: result}) - return Observable.empty() - }) - }, atom.config.get('textual-velocity.concurrentFileReads')) + const readAsObservable = Observable.bindNodeCallback( + fileReader.read.bind(fileReader) + ); + return readAsObservable(path, rawFile.stats) + .map(readFileValue => { + result.value = readFileValue !== undefined ? readFileValue : null; // make sure value cannot be undefined + return A.fileRead(result); + }) + .catch(err => { + console.warn("failed to read file:", { err, context: result }); + return Observable.empty(); + }); + }, atom.config.get("textual-velocity.concurrentFileReads")); } diff --git a/lib/epics/file-writes.js b/lib/epics/file-writes.js index f470fbd..4a4bb19 100644 --- a/lib/epics/file-writes.js +++ b/lib/epics/file-writes.js @@ -1,37 +1,43 @@ /* @flow */ -import Path from 'path' -import * as A from '../action-creators' +import Path from "path"; +import * as A from "../action-creators"; -export default function makeFileWritesEpic (fileWriters: FileWriters) { - return function fileWritesEpic (action$: rxjs$Observable, store: Store) { +export default function makeFileWritesEpic(fileWriters: FileWriters) { + return function fileWritesEpic( + action$: rxjs$Observable, + store: Store + ) { return action$ .filter(action => { if (action.type === A.EDIT_CELL_SAVE) { - const state: State = store.getState() - const editCellName = action.editCellName + const state: State = store.getState(); + const editCellName = action.editCellName; - const fileWriter = fileWriters.find(fileWriter => fileWriter.editCellName === editCellName) + const fileWriter = fileWriters.find( + fileWriter => fileWriter.editCellName === editCellName + ); if (fileWriter && state.selectedNote) { - const path = Path.join(state.dir, state.selectedNote.filename) + const path = Path.join(state.dir, state.selectedNote.filename); fileWriter.write(path, action.value, (err, result) => { if (err) { - atom.notifications.addError(`Textual Velocity: Failed to save file ${path}:'`, { - detail: err.message, - stack: err.stack, - dismissable: true - }) + atom.notifications.addError( + `Textual Velocity: Failed to save file ${path}:'`, + { + detail: err.message, + stack: err.stack, + dismissable: true + } + ); } // write result is handled implicitly, through the path watcher change event, instead of creating an action here - }) + }); } } - return false + return false; }) - .takeUntil( - action$.filter(action => action.type === A.DISPOSE) - ) - } + .takeUntil(action$.filter(action => action.type === A.DISPOSE)); + }; } diff --git a/lib/epics/hidden-columns.js b/lib/epics/hidden-columns.js index a5ae9bd..4161f05 100644 --- a/lib/epics/hidden-columns.js +++ b/lib/epics/hidden-columns.js @@ -1,54 +1,66 @@ /* @flow */ -import Disposables from '../disposables' -import {observeConfig} from '../atom-rxjs-observables' -import {name} from '../columns' -import * as A from '../action-creators' +import Disposables from "../disposables"; +import { observeConfig } from "../atom-rxjs-observables"; +import { name } from "../columns"; +import * as A from "../action-creators"; -export default function makeHiddenColumnsEpic (columns: Columns) { - let contextMenuDisposable +export default function makeHiddenColumnsEpic(columns: Columns) { + let contextMenuDisposable; - return function hiddenColumnsEpic (action$: rxjs$Observable, store: Store) { + return function hiddenColumnsEpic( + action$: rxjs$Observable, + store: Store + ) { const disposableCommands = new Disposables( ...columns.map(column => { - const columnName = name(column) - return atom.commands.add('atom-workspace', `textual-velocity:toggle-${columnName}-column`, () => { - const hiddenColumns = atom.config.get('textual-velocity.hiddenColumns') - atom.config.set('textual-velocity.hiddenColumns', hiddenColumns.includes(columnName) - ? hiddenColumns.filter(name => name !== columnName) - : [...hiddenColumns, columnName]) - }) + const columnName = name(column); + return atom.commands.add( + "atom-workspace", + `textual-velocity:toggle-${columnName}-column`, + () => { + const hiddenColumns = atom.config.get( + "textual-velocity.hiddenColumns" + ); + atom.config.set( + "textual-velocity.hiddenColumns", + hiddenColumns.includes(columnName) + ? hiddenColumns.filter(name => name !== columnName) + : [...hiddenColumns, columnName] + ); + } + ); }) - ) + ); - return observeConfig('textual-velocity.hiddenColumns') - .map((hiddenColumns) => { + return observeConfig("textual-velocity.hiddenColumns") + .map(hiddenColumns => { if (contextMenuDisposable) { - contextMenuDisposable.dispose() + contextMenuDisposable.dispose(); } contextMenuDisposable = atom.contextMenu.add({ - '.textual-velocity .header': columns - .map(column => { - const columnName = name(column) - return { - label: `${hiddenColumns.includes(columnName) ? ' ' : '✓'} ${column.title}`, - command: `textual-velocity:toggle-${columnName}-column` - } - }) - }) + ".textual-velocity .header": columns.map(column => { + const columnName = name(column); + return { + label: `${hiddenColumns.includes(columnName) + ? " " + : "✓"} ${column.title}`, + command: `textual-velocity:toggle-${columnName}-column` + }; + }) + }); - return A.changeHiddenColumns(hiddenColumns) + return A.changeHiddenColumns(hiddenColumns); }) .takeUntil( - action$ - .filter(action => action.type === A.DISPOSE) - .do(() => { - if (contextMenuDisposable) { - contextMenuDisposable.dispose() - } - contextMenuDisposable = null - disposableCommands.dispose() - })) - } + action$.filter(action => action.type === A.DISPOSE).do(() => { + if (contextMenuDisposable) { + contextMenuDisposable.dispose(); + } + contextMenuDisposable = null; + disposableCommands.dispose(); + }) + ); + }; } diff --git a/lib/epics/index.js b/lib/epics/index.js index 401c496..d31e6a7 100644 --- a/lib/epics/index.js +++ b/lib/epics/index.js @@ -1,19 +1,23 @@ /* @flow */ -import {combineEpics, createEpicMiddleware} from 'redux-observable' -import activePaneItem from './active-pane-item' -import configChanges from './config-changes' -import makeHiddenColumns from './hidden-columns' -import makeFileReads from './file-reads' -import makeFileWrites from './file-writes' -import pathWatcher from './path-watcher' -import previewNote from './preview-note' -import atCopyMatchToClipboardEpic from './at-copy-match-to-clipboard' +import { combineEpics, createEpicMiddleware } from "redux-observable"; +import activePaneItem from "./active-pane-item"; +import configChanges from "./config-changes"; +import makeHiddenColumns from "./hidden-columns"; +import makeFileReads from "./file-reads"; +import makeFileWrites from "./file-writes"; +import pathWatcher from "./path-watcher"; +import previewNote from "./preview-note"; +import atCopyMatchToClipboardEpic from "./at-copy-match-to-clipboard"; -export default function makeEpicMiddleware (columns: Columns, fileReaders: FileReaders, fileWriters: FileWriters) { - const fileReads = makeFileReads(fileReaders) - const fileWrites = makeFileWrites(fileWriters) - const hiddenColumns = makeHiddenColumns(columns) +export default function makeEpicMiddleware( + columns: Columns, + fileReaders: FileReaders, + fileWriters: FileWriters +) { + const fileReads = makeFileReads(fileReaders); + const fileWrites = makeFileWrites(fileWriters); + const hiddenColumns = makeHiddenColumns(columns); return createEpicMiddleware( combineEpics( @@ -26,5 +30,5 @@ export default function makeEpicMiddleware (columns: Columns, fileReaders: FileR previewNote, atCopyMatchToClipboardEpic ) - ) + ); } diff --git a/lib/epics/path-watcher.js b/lib/epics/path-watcher.js index faad019..8f1a200 100644 --- a/lib/epics/path-watcher.js +++ b/lib/epics/path-watcher.js @@ -1,84 +1,93 @@ /* @flow */ -import chokidar from 'chokidar' -import {Observable} from 'rxjs' -import Path from 'path' -import {Task} from 'atom' -import * as A from '../action-creators' -import NotesFileFilter from '../notes-file-filter' +import chokidar from "chokidar"; +import { Observable } from "rxjs"; +import Path from "path"; +import { Task } from "atom"; +import * as A from "../action-creators"; +import NotesFileFilter from "../notes-file-filter"; -export default function pathWatcherEpic (action$: rxjs$Observable, store: Store) { - const dir = store.getState().dir +export default function pathWatcherEpic( + action$: rxjs$Observable, + store: Store +) { + const dir = store.getState().dir; const fileFilter = new NotesFileFilter(dir, { - exclusions: atom.config.get('textual-velocity.ignoredNames'), - excludeVcsIgnoredPaths: atom.config.get('textual-velocity.excludeVcsIgnoredPaths') - }) + exclusions: atom.config.get("textual-velocity.ignoredNames"), + excludeVcsIgnoredPaths: atom.config.get( + "textual-velocity.excludeVcsIgnoredPaths" + ) + }); - const filterAndCreateAction = (actionCreator: (rawFile: RawFile) => Action|void) => (rawFile: RawFile) => { - const path = Path.join(dir, rawFile.filename) + const filterAndCreateAction = ( + actionCreator: (rawFile: RawFile) => Action | void + ) => (rawFile: RawFile) => { + const path = Path.join(dir, rawFile.filename); if (fileFilter.isAccepted(path)) { - const {stats} = rawFile - if (typeof stats.atime === 'string') { - stats.atime = new Date(stats.atime) - stats.birthtime = new Date(stats.birthtime) - stats.ctime = new Date(stats.ctime) - stats.mtime = new Date(stats.mtime) + const { stats } = rawFile; + if (typeof stats.atime === "string") { + stats.atime = new Date(stats.atime); + stats.birthtime = new Date(stats.birthtime); + stats.ctime = new Date(stats.ctime); + stats.mtime = new Date(stats.mtime); } - return actionCreator(rawFile) + return actionCreator(rawFile); } - } - const filterAndCreateAddAction = filterAndCreateAction(A.fileAdded) - const filterAndCreateChangeAction = filterAndCreateAction(A.fileChanged) + }; + const filterAndCreateAddAction = filterAndCreateAction(A.fileAdded); + const filterAndCreateChangeAction = filterAndCreateAction(A.fileChanged); const chokidarOptions: ChokidarOptions = { alwaysStat: true, cwd: dir, depth: 0, - ignored: 'node_modules' - } + ignored: "node_modules" + }; const chokidarWatch = chokidar.watch(dir, { ...chokidarOptions, ignoreInitial: true, // initial scan is handled by initialScanTask instead, to not completely block the UI thread persistent: true - }) - chokidarWatch.off = () => {} // makes task compatible with expected format for Observable.fromEvent - - const initialScanTask = new Task(Path.join(__dirname, '..', 'initial-scan-task.js')) - initialScanTask.off = () => {} // makes task compatible with expected format for Observable.fromEvent + }); + chokidarWatch.off = () => {}; // makes task compatible with expected format for Observable.fromEvent - return Observable - .merge( - action$.filter(action => { - if (action.type === A.START_INITIAL_SCAN) { - initialScanTask.start(chokidarOptions) - } - return false - }), - Observable.fromEvent(initialScanTask, 'add').map(filterAndCreateAddAction), - Observable.fromEvent(initialScanTask, 'ready').mapTo(A.initialScanDone()), + const initialScanTask = new Task( + Path.join(__dirname, "..", "initial-scan-task.js") + ); + initialScanTask.off = () => {}; // makes task compatible with expected format for Observable.fromEvent - Observable.fromEvent(chokidarWatch, 'add', argsToRawFile).map(filterAndCreateAddAction), - Observable.fromEvent(chokidarWatch, 'change', argsToRawFile).map(filterAndCreateChangeAction), - Observable.fromEvent(chokidarWatch, 'unlink').map(A.fileDeleted) - ) + return Observable.merge( + action$.filter(action => { + if (action.type === A.START_INITIAL_SCAN) { + initialScanTask.start(chokidarOptions); + } + return false; + }), + Observable.fromEvent(initialScanTask, "add").map(filterAndCreateAddAction), + Observable.fromEvent(initialScanTask, "ready").mapTo(A.initialScanDone()), + Observable.fromEvent(chokidarWatch, "add", argsToRawFile).map( + filterAndCreateAddAction + ), + Observable.fromEvent(chokidarWatch, "change", argsToRawFile).map( + filterAndCreateChangeAction + ), + Observable.fromEvent(chokidarWatch, "unlink").map(A.fileDeleted) + ) .filter(action => !!action) .takeUntil( - action$ - .filter(action => action.type === A.DISPOSE) - .do(() => { - chokidarWatch.close() - try { - initialScanTask.send('dispose') - } catch (err) { - // throws error if task already is terminated - } - initialScanTask.terminate() - }) - ) + action$.filter(action => action.type === A.DISPOSE).do(() => { + chokidarWatch.close(); + try { + initialScanTask.send("dispose"); + } catch (err) { + // throws error if task already is terminated + } + initialScanTask.terminate(); + }) + ); } -function argsToRawFile (filename: string, stats: FsStats) { - const file: RawFile = {filename, stats} - return file +function argsToRawFile(filename: string, stats: FsStats) { + const file: RawFile = { filename, stats }; + return file; } diff --git a/lib/epics/preview-note.js b/lib/epics/preview-note.js index f0c27db..e715794 100644 --- a/lib/epics/preview-note.js +++ b/lib/epics/preview-note.js @@ -1,125 +1,122 @@ /* @flow */ -import {Observable} from 'rxjs' -import Path from 'path' -import PreviewElement from '../preview-element' -import * as A from '../action-creators' +import { Observable } from "rxjs"; +import Path from "path"; +import PreviewElement from "../preview-element"; +import * as A from "../action-creators"; -const PREVIEW_SCHEMA_PREFIX = 'tv://' -const HAS_FILE_EXT_REGEX = /\.\w{1,5}$/ +const PREVIEW_SCHEMA_PREFIX = "tv://"; +const HAS_FILE_EXT_REGEX = /\.\w{1,5}$/; -export default function previewNoteEpic (action$: rxjs$Observable, store: Store) { - const previewElement = new PreviewElement() +export default function previewNoteEpic( + action$: rxjs$Observable, + store: Store +) { + const previewElement = new PreviewElement(); - const openerDisposable = atom.workspace.addOpener((uri) => { + const openerDisposable = atom.workspace.addOpener(uri => { if (uri.startsWith(PREVIEW_SCHEMA_PREFIX)) { - return previewElement + return previewElement; } - }) + }); const closePreview = () => { - const pane = atom.workspace.paneForItem(previewElement) + const pane = atom.workspace.paneForItem(previewElement); if (pane) { - pane.destroyItem(previewElement) + pane.destroyItem(previewElement); } - } - - return Observable - .merge( - - // Open/close preview based on selectedNote state - action$ - .debounceTime(25) // ms - .map(() => store.getState()) - .distinctUntilChanged((a: Store, b: Store) => { - return (a.selectedNote && a.selectedNote.filename) === (b.selectedNote && b.selectedNote.filename) - }) - .filter(() => { - const state: State = store.getState() - if (!state.selectedNote) { - closePreview() - return false - } - - const filename = state.selectedNote.filename - const path = Path.join(state.dir, filename) - - for (let textEditor of atom.workspace.getTextEditors()) { - if (textEditor.getPath() === path) { - // there is already a text-editor use that as preview instead - closePreview() - atom.workspace.open(path, { - activatePane: false, - searchAllPanes: true - }) - return false - } - } - - const note = state.notes[filename] - if (note) { - const searchRegex = state.sifterResult.tokens[0] && state.sifterResult.tokens[0].regex - previewElement.updatePreview(path, note.content || '', searchRegex) - - atom.workspace - .open(PREVIEW_SCHEMA_PREFIX + path, { - activatePane: false, - searchAllPanes: true - }) - .then(() => { - previewElement.scrollToFirstHighlightedItem() - }) + }; + + return Observable.merge( + // Open/close preview based on selectedNote state + action$ + .debounceTime(25) // ms + .map(() => store.getState()) + .distinctUntilChanged((a: Store, b: Store) => { + return ( + (a.selectedNote && a.selectedNote.filename) === + (b.selectedNote && b.selectedNote.filename) + ); + }) + .filter(() => { + const state: State = store.getState(); + if (!state.selectedNote) { + closePreview(); + return false; + } + + const filename = state.selectedNote.filename; + const path = Path.join(state.dir, filename); + + for (let textEditor of atom.workspace.getTextEditors()) { + if (textEditor.getPath() === path) { + // there is already a text-editor use that as preview instead + closePreview(); + atom.workspace.open(path, { + activatePane: false, + searchAllPanes: true + }); + return false; } + } - return false - }), + const note = state.notes[filename]; + if (note) { + const searchRegex = + state.sifterResult.tokens[0] && state.sifterResult.tokens[0].regex; + previewElement.updatePreview(path, note.content || "", searchRegex); - // Open on specific open-note action - action$ - .filter(action => { - if (action.type === A.OPEN_NOTE) { - const state: State = store.getState() - let filename - - if (state.selectedNote) { - filename = state.selectedNote.filename - } else { - filename = state.queryOriginal.trim() || 'untitled' - - if (!HAS_FILE_EXT_REGEX.test(filename)) { - const ext = atom.config.get('textual-velocity.defaultExt') - .replace(/^\./, '') // avoid double dots next to extension, i.e. untitled..txt => untitled.txt - filename = `${filename}.${ext}` - } - } - - atom.workspace - .open(Path.join(state.dir, filename)) - .then(closePreview) - } - - return false - }), - - // Replace preview with default editor when clicked - Observable - .fromEvent(previewElement, 'click') - .filter(() => { atom.workspace - .open(previewElement.getPath()) - .then(closePreview) - return false - }) - ) // .merge - .takeUntil( - action$ - .filter(action => { - const isDisposeAction = action.type === A.DISPOSE - if (isDisposeAction) { - closePreview() - openerDisposable.dispose() - previewElement.dispose() + .open(PREVIEW_SCHEMA_PREFIX + path, { + activatePane: false, + searchAllPanes: true + }) + .then(() => { + previewElement.scrollToFirstHighlightedItem(); + }); + } + + return false; + }), + // Open on specific open-note action + action$.filter(action => { + if (action.type === A.OPEN_NOTE) { + const state: State = store.getState(); + let filename; + + if (state.selectedNote) { + filename = state.selectedNote.filename; + } else { + filename = state.queryOriginal.trim() || "untitled"; + + if (!HAS_FILE_EXT_REGEX.test(filename)) { + const ext = atom.config + .get("textual-velocity.defaultExt") + .replace(/^\./, ""); // avoid double dots next to extension, i.e. untitled..txt => untitled.txt + filename = `${filename}.${ext}`; } - return isDisposeAction - })) + } + + atom.workspace.open(Path.join(state.dir, filename)).then(closePreview); + } + + return false; + }), + // Replace preview with default editor when clicked + Observable.fromEvent(previewElement, "click").filter(() => { + atom.workspace.open(previewElement.getPath()).then(closePreview); + return false; + }) + ) // .merge + .takeUntil( + action$.filter(action => { + const isDisposeAction = action.type === A.DISPOSE; + if (isDisposeAction) { + closePreview(); + openerDisposable.dispose(); + previewElement.dispose(); + } + return isDisposeAction; + }) + ); } diff --git a/lib/fields/parsed-path-field.js b/lib/fields/parsed-path-field.js index 2b963f6..6f69fe0 100644 --- a/lib/fields/parsed-path-field.js +++ b/lib/fields/parsed-path-field.js @@ -1,35 +1,35 @@ /* @flow */ -import Path from 'path' +import Path from "path"; export default class ParsedPathField { - notePropName: string - _parsedPathPropName: string + notePropName: string; + _parsedPathPropName: string; - value: void | (note: Note, filename: string) => any + value: void | ((note: Note, filename: string) => any); - constructor (params: {notePropName: string, parsedPathPropName: string}) { - this.notePropName = params.notePropName - this._parsedPathPropName = params.parsedPathPropName + constructor(params: { notePropName: string, parsedPathPropName: string }) { + this.notePropName = params.notePropName; + this._parsedPathPropName = params.parsedPathPropName; } - value (note: Note, filename: string): any { - return Path - .parse(filename)[this._parsedPathPropName] - - // MacOSX encodes non-ASCII characters by combined form, that although is rendered in the same way has a - // different representation in memory, so called "unicode codepoints". - // - // E.g. a filename named 'älg.txt' would store the "ä" using the two code points: - // a is https://codepoints.net/U+0061 aka LATIN SMALL LETTER A - // ¨ is https://codepoints.net/U+308 aka COMBINING DIAERESIS - // - // the normalize call here makes sure the string is using the single character representation instead, - // i.e. https://codepoints.net/U+00E4 aka LATIN SMALL LETTER A WITH DIAERESIS - // - // References: - // - https://developer.apple.com/library/content/qa/qa1173/_index.html - // - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize - .normalize('NFC') + value(note: Note, filename: string): any { + return ( + Path.parse(filename)[this._parsedPathPropName] + // MacOSX encodes non-ASCII characters by combined form, that although is rendered in the same way has a + // different representation in memory, so called "unicode codepoints". + // + // E.g. a filename named 'älg.txt' would store the "ä" using the two code points: + // a is https://codepoints.net/U+0061 aka LATIN SMALL LETTER A + // ¨ is https://codepoints.net/U+308 aka COMBINING DIAERESIS + // + // the normalize call here makes sure the string is using the single character representation instead, + // i.e. https://codepoints.net/U+00E4 aka LATIN SMALL LETTER A WITH DIAERESIS + // + // References: + // - https://developer.apple.com/library/content/qa/qa1173/_index.html + // - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/normalize + .normalize("NFC") + ); } } diff --git a/lib/fields/stats-date-field.js b/lib/fields/stats-date-field.js index 1d887ca..5ffe0f6 100644 --- a/lib/fields/stats-date-field.js +++ b/lib/fields/stats-date-field.js @@ -1,18 +1,18 @@ /* @flow */ export default class StatsDateField { - notePropName: string - _statsPropName: string + notePropName: string; + _statsPropName: string; - value: void | (note: Note, filename: string) => any + value: void | ((note: Note, filename: string) => any); - constructor (params: {notePropName: string, statsPropName: string}) { - this.notePropName = params.notePropName - this._statsPropName = params.statsPropName + constructor(params: { notePropName: string, statsPropName: string }) { + this.notePropName = params.notePropName; + this._statsPropName = params.statsPropName; } - value (note: Note, filename: string): any { - const date = note.stats && note.stats[this._statsPropName] - return date && date.getTime() + value(note: Note, filename: string): any { + const date = note.stats && note.stats[this._statsPropName]; + return date && date.getTime(); } } diff --git a/lib/file-readers.js b/lib/file-readers.js index 60e30c8..3b1f1c1 100644 --- a/lib/file-readers.js +++ b/lib/file-readers.js @@ -1,48 +1,51 @@ /* @flow */ -import logError from './log-error' +import logError from "./log-error"; -const privates = new WeakMap() +const privates = new WeakMap(); export default class FileReaders { - constructor () { - privates.set(this, []) + constructor() { + privates.set(this, []); } - add (fileReader: FileReader) { - if (typeof fileReader !== 'object') return logError('fileReader object is required', fileReader) - if (typeof fileReader.notePropName !== 'string') return logError('fileReader.notePropName string is required', fileReader) - if (typeof fileReader.read !== 'function') return logError('fileReader.read function is required', fileReader) + add(fileReader: FileReader) { + if (typeof fileReader !== "object") + return logError("fileReader object is required", fileReader); + if (typeof fileReader.notePropName !== "string") + return logError("fileReader.notePropName string is required", fileReader); + if (typeof fileReader.read !== "function") + return logError("fileReader.read function is required", fileReader); - const fileReaders = privates.get(this) || [] - fileReaders.push(fileReader) + const fileReaders = privates.get(this) || []; + fileReaders.push(fileReader); } - remove (fileReader: FileReader) { - const fileReaders = privates.get(this) || [] - const index = fileReaders.indexOf(fileReader) + remove(fileReader: FileReader) { + const fileReaders = privates.get(this) || []; + const index = fileReaders.indexOf(fileReader); if (index >= 0) { - fileReaders.splice(index, 1) + fileReaders.splice(index, 1); } } - every (predicate: (fileReader: FileReader) => boolean) { - return (privates.get(this) || []).every(predicate) + every(predicate: (fileReader: FileReader) => boolean) { + return (privates.get(this) || []).every(predicate); } - filter (predicate: (fileReader: FileReader) => boolean) { - return (privates.get(this) || []).filter(predicate) + filter(predicate: (fileReader: FileReader) => boolean) { + return (privates.get(this) || []).filter(predicate); } - forEach (callback: (fileReader: FileReader) => void): void { - (privates.get(this) || []).forEach(callback) + forEach(callback: (fileReader: FileReader) => void): void { + (privates.get(this) || []).forEach(callback); } - map (mapper: (fileReader: FileReader) => T): Array { - return (privates.get(this) || []).map(mapper) + map(mapper: (fileReader: FileReader) => T): Array { + return (privates.get(this) || []).map(mapper); } - dispose () { - privates.delete(this) + dispose() { + privates.delete(this); } } diff --git a/lib/file-readers/content-file-reader.js b/lib/file-readers/content-file-reader.js index 855a811..a1e4575 100644 --- a/lib/file-readers/content-file-reader.js +++ b/lib/file-readers/content-file-reader.js @@ -1,12 +1,11 @@ /* @flow */ -import fs from 'fs' +import fs from "fs"; export default { + notePropName: "content", - notePropName: 'content', - - read (path: string, stats: FsStats, callback: NodeCallback) { - fs.readFile(path, 'utf8', callback) + read(path: string, stats: FsStats, callback: NodeCallback) { + fs.readFile(path, "utf8", callback); } -} +}; diff --git a/lib/file-readers/file-icons-reader.js b/lib/file-readers/file-icons-reader.js index 854edbb..635d2b8 100644 --- a/lib/file-readers/file-icons-reader.js +++ b/lib/file-readers/file-icons-reader.js @@ -2,23 +2,25 @@ // File reader for integration with https://atom.io/packages/file-icons export default class FileIconsReader { - notePropName: string - _fileIconsService: {iconClassForPath: (path: string) => Array} + notePropName: string; + _fileIconsService: { iconClassForPath: (path: string) => Array }; - constructor (fileIconsService: {iconClassForPath: (path: string) => Array}) { - this._fileIconsService = fileIconsService - this.notePropName = 'fileIcons' + constructor(fileIconsService: { + iconClassForPath: (path: string) => Array + }) { + this._fileIconsService = fileIconsService; + this.notePropName = "fileIcons"; } - read (path: string, stats: FsStats, callback: NodeCallback) { - let classNames = this._fileIconsService.iconClassForPath(path) + read(path: string, stats: FsStats, callback: NodeCallback) { + let classNames = this._fileIconsService.iconClassForPath(path); if (classNames instanceof Array) { - classNames = classNames.join(' ') - } else if (typeof classNames !== 'string') { - classNames = null + classNames = classNames.join(" "); + } else if (typeof classNames !== "string") { + classNames = null; } - callback(null, classNames) + callback(null, classNames); } } diff --git a/lib/file-readers/stats-file-reader.js b/lib/file-readers/stats-file-reader.js index 6ea1404..e4a8f0b 100644 --- a/lib/file-readers/stats-file-reader.js +++ b/lib/file-readers/stats-file-reader.js @@ -1,10 +1,9 @@ /* @flow */ export default { + notePropName: "stats", - notePropName: 'stats', - - read (path: string, stats: FsStats, callback: NodeCallback) { - callback(null, stats) + read(path: string, stats: FsStats, callback: NodeCallback) { + callback(null, stats); } -} +}; diff --git a/lib/file-writers.js b/lib/file-writers.js index 4a4b113..68a8efe 100644 --- a/lib/file-writers.js +++ b/lib/file-writers.js @@ -1,28 +1,31 @@ /* @flow */ -import logError from './log-error' +import logError from "./log-error"; -const privates = new WeakMap() +const privates = new WeakMap(); export default class FileWriters { - constructor () { - privates.set(this, []) + constructor() { + privates.set(this, []); } - add (fileWriter: FileWriter) { - if (typeof fileWriter !== 'object') return logError('fileWriter object is required', fileWriter) - if (typeof fileWriter.editCellName !== 'string') return logError('fileWriter.editCellName string is required', fileWriter) - if (typeof fileWriter.write !== 'function') return logError('fileWriter.write function is required', fileWriter) + add(fileWriter: FileWriter) { + if (typeof fileWriter !== "object") + return logError("fileWriter object is required", fileWriter); + if (typeof fileWriter.editCellName !== "string") + return logError("fileWriter.editCellName string is required", fileWriter); + if (typeof fileWriter.write !== "function") + return logError("fileWriter.write function is required", fileWriter); - const fileWriters = privates.get(this) || [] - fileWriters.push(fileWriter) + const fileWriters = privates.get(this) || []; + fileWriters.push(fileWriter); } - find (predicate: (fileWriter: FileWriter) => boolean) { - return (privates.get(this) || []).find(predicate) + find(predicate: (fileWriter: FileWriter) => boolean) { + return (privates.get(this) || []).find(predicate); } - dispose () { - privates.delete(this) + dispose() { + privates.delete(this); } } diff --git a/lib/get-valid-dir-from-path.js b/lib/get-valid-dir-from-path.js index 065e33c..baabe34 100644 --- a/lib/get-valid-dir-from-path.js +++ b/lib/get-valid-dir-from-path.js @@ -1,24 +1,24 @@ /* @flow */ -import Path from 'path' -import fs from 'fs-plus' +import Path from "path"; +import fs from "fs-plus"; -export default function validatePath (path: string) { - path = path.trim() +export default function validatePath(path: string) { + path = path.trim(); - if (path === '') { - return Path.join(atom.configDirPath, 'notes') + if (path === "") { + return Path.join(atom.configDirPath, "notes"); } - path = fs.absolute(path) // e.g. ~/something => /Users/alice/something + path = fs.absolute(path); // e.g. ~/something => /Users/alice/something if (!fs.isAbsolute(path)) { - path = Path.join(fs.getHomeDirectory(), path) + path = Path.join(fs.getHomeDirectory(), path); } - path = Path.resolve(path) // removes any trailing slash + path = Path.resolve(path); // removes any trailing slash - // TODO workflow for "faulty" path, e.g. can't read dir etc. should degrade gracefully or hint user how to resolve + // TODO workflow for "faulty" path, e.g. can't read dir etc. should degrade gracefully or hint user how to resolve - return path + return path; } diff --git a/lib/initial-scan-task.js b/lib/initial-scan-task.js index c0492d3..f267cdd 100644 --- a/lib/initial-scan-task.js +++ b/lib/initial-scan-task.js @@ -1,34 +1,34 @@ /* @flow */ /* global emit */ -import chokidar from 'chokidar' +import chokidar from "chokidar"; // Scan path in a separate process to not block UI thread -export default function (chokidarOptions: ChokidarOptions) { +export default function(chokidarOptions: ChokidarOptions) { chokidarOptions = { ...chokidarOptions, ignoreInitial: false, persistent: false - } - const chokidarWatch = chokidar.watch(chokidarOptions.cwd, chokidarOptions) + }; + const chokidarWatch = chokidar.watch(chokidarOptions.cwd, chokidarOptions); - const done = this.async() + const done = this.async(); const dispose = () => { - chokidarWatch.close() // make sure to cleanup if process is terminated - done() - } + chokidarWatch.close(); // make sure to cleanup if process is terminated + done(); + }; - chokidarWatch.on('add', (filename: string, stats: FsStats) => { - emit('add', ({filename, stats}: RawFile)) - }) + chokidarWatch.on("add", (filename: string, stats: FsStats) => { + emit("add", ({ filename, stats }: RawFile)); + }); - chokidarWatch.on('ready', () => { - emit('ready') - dispose() - }) + chokidarWatch.on("ready", () => { + emit("ready"); + dispose(); + }); - process.on('message', () => { + process.on("message", () => { // finished prematurely, e.g. user deactivates session before the task has finished, or similar - dispose() - }) + dispose(); + }); } diff --git a/lib/log-error.js b/lib/log-error.js index b9bf70c..d7f5e1c 100644 --- a/lib/log-error.js +++ b/lib/log-error.js @@ -1,5 +1,5 @@ /* @flow */ -export default function logError (msg: string, obj: Object) { - console.error(`${msg}, was ${JSON.stringify(obj)}`) +export default function logError(msg: string, obj: Object) { + console.error(`${msg}, was ${JSON.stringify(obj)}`); } diff --git a/lib/main.js b/lib/main.js index f64865f..3a7e3c7 100644 --- a/lib/main.js +++ b/lib/main.js @@ -1,125 +1,137 @@ /* @flow */ -import Disposables from './disposables' -import Columns from './columns' -import FileReaders from './file-readers' -import FileWriters from './file-writers' -import NoteFields from './note-fields' -import Service from './service' -import Session from './session' -import nvTags from './service-consumers/nv-tags' -import renameNote from './service-consumers/rename-note' -import defaults from './service-consumers/defaults' -import defaultConfig from './default-config' -import FileIconsReader from './file-readers/file-icons-reader' -import getValidDirFromPath from './get-valid-dir-from-path' - -const RENAME_CELL_NAME = 'rename' - -export const config = defaultConfig - -let disposables, columns, fileReaders, fileWriters, noteFields, service, session, sessionCmds, startSessionCmd - -export function activate () { - columns = new Columns() - fileReaders = new FileReaders() - fileWriters = new FileWriters() - noteFields = new NoteFields() - service = new Service(columns, fileReaders, fileWriters, noteFields) +import Disposables from "./disposables"; +import Columns from "./columns"; +import FileReaders from "./file-readers"; +import FileWriters from "./file-writers"; +import NoteFields from "./note-fields"; +import Service from "./service"; +import Session from "./session"; +import nvTags from "./service-consumers/nv-tags"; +import renameNote from "./service-consumers/rename-note"; +import defaults from "./service-consumers/defaults"; +import defaultConfig from "./default-config"; +import FileIconsReader from "./file-readers/file-icons-reader"; +import getValidDirFromPath from "./get-valid-dir-from-path"; + +const RENAME_CELL_NAME = "rename"; + +export const config = defaultConfig; + +let disposables, + columns, + fileReaders, + fileWriters, + noteFields, + service, + session, + sessionCmds, + startSessionCmd; + +export function activate() { + columns = new Columns(); + fileReaders = new FileReaders(); + fileWriters = new FileWriters(); + noteFields = new NoteFields(); + service = new Service(columns, fileReaders, fileWriters, noteFields); disposables = new Disposables( defaults.consumeService(service, RENAME_CELL_NAME), renameNote.consumeService(service, RENAME_CELL_NAME), nvTags.consumeService(service) - ) + ); - startSession() + startSession(); } // Integration with https://atom.io/packages/file-icons -export function consumeFileIconsService (fileIconsService: any) { - const fileIconsReader = new FileIconsReader(fileIconsService) +export function consumeFileIconsService(fileIconsService: any) { + const fileIconsReader = new FileIconsReader(fileIconsService); if (service) { - service.registerFileReaders(fileIconsReader) + service.registerFileReaders(fileIconsReader); } return new Disposables(() => { if (service) { - service.deregisterFileReaders(fileIconsReader) + service.deregisterFileReaders(fileIconsReader); } - }) + }); } -export function provideService () { - return service +export function provideService() { + return service; } -export function deactivate () { - stopSession() - disposeStartSessionCmd() +export function deactivate() { + stopSession(); + disposeStartSessionCmd(); if (disposables) { - disposables.dispose() - disposables = null + disposables.dispose(); + disposables = null; } if (service) { - service.dispose() - service = null + service.dispose(); + service = null; } if (columns) { - columns.dispose() - columns = null + columns.dispose(); + columns = null; } if (fileReaders) { - fileReaders.dispose() - fileReaders = null + fileReaders.dispose(); + fileReaders = null; } if (fileWriters) { - fileWriters.dispose() - fileWriters = null + fileWriters.dispose(); + fileWriters = null; } if (noteFields) { - noteFields.dispose() - noteFields = null + noteFields.dispose(); + noteFields = null; } } -function startSession () { - const dir = getValidDirFromPath(atom.config.get('textual-velocity.path')) +function startSession() { + const dir = getValidDirFromPath(atom.config.get("textual-velocity.path")); - disposeStartSessionCmd() - if (!columns || !fileReaders || !fileWriters || !noteFields) return + disposeStartSessionCmd(); + if (!columns || !fileReaders || !fileWriters || !noteFields) return; - session = new Session() - session.start(dir, columns, fileReaders, fileWriters, noteFields) + session = new Session(); + session.start(dir, columns, fileReaders, fileWriters, noteFields); - sessionCmds = atom.commands.add('atom-workspace', { - 'textual-velocity:restart-session': () => { - stopSession() - startSession() + sessionCmds = atom.commands.add("atom-workspace", { + "textual-velocity:restart-session": () => { + stopSession(); + startSession(); }, - 'textual-velocity:stop-session': () => { - stopSession() - startSessionCmd = atom.commands.add('atom-workspace', 'textual-velocity:start-session', startSession) + "textual-velocity:stop-session": () => { + stopSession(); + startSessionCmd = atom.commands.add( + "atom-workspace", + "textual-velocity:start-session", + startSession + ); } - }) + }); } -function stopSession () { +function stopSession() { if (sessionCmds) { - sessionCmds.dispose() - sessionCmds = null + sessionCmds.dispose(); + sessionCmds = null; } if (session) { - session.dispose() - session = null + session.dispose(); + session = null; } } -function disposeStartSessionCmd () { +function disposeStartSessionCmd() { if (startSessionCmd) { - startSessionCmd.dispose() - startSessionCmd = null + startSessionCmd.dispose(); + startSessionCmd = null; } } diff --git a/lib/note-fields.js b/lib/note-fields.js index 92bd200..189d593 100644 --- a/lib/note-fields.js +++ b/lib/note-fields.js @@ -1,31 +1,33 @@ /* @flow */ -import logError from './log-error' +import logError from "./log-error"; -const privates = new WeakMap() +const privates = new WeakMap(); export default class NoteFields { - constructor () { - privates.set(this, []) + constructor() { + privates.set(this, []); } - add (field: NoteField) { - if (typeof field !== 'object') return logError('field object is required', field) - if (typeof field.notePropName !== 'string') return logError('field.notePropName string is required', field) + add(field: NoteField) { + if (typeof field !== "object") + return logError("field object is required", field); + if (typeof field.notePropName !== "string") + return logError("field.notePropName string is required", field); - const fields = privates.get(this) || [] - fields.push(field) + const fields = privates.get(this) || []; + fields.push(field); } - forEach (callback: (noteField: NoteField) => T) { - (privates.get(this) || []).forEach(callback) + forEach(callback: (noteField: NoteField) => T) { + (privates.get(this) || []).forEach(callback); } - map (mapper: (noteField: NoteField) => T): Array { - return (privates.get(this) || []).map(mapper) + map(mapper: (noteField: NoteField) => T): Array { + return (privates.get(this) || []).map(mapper); } - dispose () { - privates.delete(this) + dispose() { + privates.delete(this); } } diff --git a/lib/notes-cache.js b/lib/notes-cache.js index e42eacf..9798cb1 100644 --- a/lib/notes-cache.js +++ b/lib/notes-cache.js @@ -1,87 +1,94 @@ /* @flow */ -import Disposables from './disposables' +import Disposables from "./disposables"; -const CACHE_VERSION = 1 -const CUSTOM_STATE_KEY = ['textual-velocity'] +const CACHE_VERSION = 1; +const CUSTOM_STATE_KEY = ["textual-velocity"]; -const privates = new WeakMap() +const privates = new WeakMap(); /** * Utilize Atom's existing atom.stateStore object to cache notes globally instead of just for current Atom session * https://github.com/atom/atom/blob/ef6b3646050261fd71452b870cc0065befe9cfcb/src/atom-environment.coffee#L141 */ export default class NotesCache { - constructor (dir: string) { + constructor(dir: string) { const disposables = new Disposables( - atom.commands.add('atom-workspace', 'textual-velocity:clear-notes-cache', () => { - privates.set(this, { - ...privates.get(this), - skipSave: true - }) + atom.commands.add( + "atom-workspace", + "textual-velocity:clear-notes-cache", + () => { + privates.set(this, { + ...privates.get(this), + skipSave: true + }); - atom.notifications.addSuccess('Textual Velocity', { - description: 'Notes cache cleared! Will take effect when the session is restarted or notes path is changed.', - dismissable: true - }) - }) - ) + atom.notifications.addSuccess("Textual Velocity", { + description: + "Notes cache cleared! Will take effect when the session is restarted or notes path is changed.", + dismissable: true + }); + } + ) + ); privates.set(this, { dir, disposables, skipSave: false - }) + }); } - dispose () { - const {disposables} = privates.get(this) || {} - disposables.dispose() - privates.delete(this) + dispose() { + const { disposables } = privates.get(this) || {}; + disposables.dispose(); + privates.delete(this); } - load () { - const {dir} = privates.get(this) || {} + load() { + const { dir } = privates.get(this) || {}; return new Promise((resolve, reject) => { - const fallback = {} - if (!atom.enablePersistence) resolve(fallback) + const fallback = {}; + if (!atom.enablePersistence) resolve(fallback); return atom.stateStore .load(atom.getStateKey(CUSTOM_STATE_KEY)) .then(state => { - const notes: Notes = (state && state[CACHE_VERSION] && state[CACHE_VERSION][dir]) || fallback - resolve(notes) + const notes: Notes = + (state && state[CACHE_VERSION] && state[CACHE_VERSION][dir]) || + fallback; + resolve(notes); }) .catch(err => { - console.warn('textual-velocity: could not load cached notes:', err) - resolve({}) - }) - }) + console.warn("textual-velocity: could not load cached notes:", err); + resolve({}); + }); + }); } - save (notes: Notes) { + save(notes: Notes) { if (!atom.enablePersistence) { return new Promise((resolve, reject) => { - reject(new Error('atom.enablePersistence is set to false')) - }) + reject(new Error("atom.enablePersistence is set to false")); + }); } - const {dir, skipSave} = privates.get(this) || {} + const { dir, skipSave } = privates.get(this) || {}; - let state = {} + let state = {}; if (!skipSave) { state = { [CACHE_VERSION]: { [dir]: notes } - } + }; } return atom.stateStore .save(atom.getStateKey(CUSTOM_STATE_KEY), state) .catch(err => { - console.warn('textual-velocity: could not save notes cache:', err) - }) + console.warn("textual-velocity: could not save notes cache:", err); + }); } } diff --git a/lib/notes-file-filter.js b/lib/notes-file-filter.js index 6b6d78d..ad0c5a4 100644 --- a/lib/notes-file-filter.js +++ b/lib/notes-file-filter.js @@ -1,26 +1,26 @@ /* @flow */ -import Path from 'path' -import fs from 'fs-plus' -import ScandalPathFilter from './scandal-path-filter' +import Path from "path"; +import fs from "fs-plus"; +import ScandalPathFilter from "./scandal-path-filter"; export default class NotesFileFilter extends ScandalPathFilter { - constructor (path: string, options: Object = {}) { - options.inclusions = ['*'] - super(path, options) + constructor(path: string, options: Object = {}) { + options.inclusions = ["*"]; + super(path, options); } - isAccepted (path: string) { - return super.isFileAccepted(path) && isTextFile(path) + isAccepted(path: string) { + return super.isFileAccepted(path) && isTextFile(path); } } -function isTextFile (path) { - const extname = Path.extname(path) +function isTextFile(path) { + const extname = Path.extname(path); return !( fs.isCompressedExtension(extname) || fs.isImageExtension(extname) || fs.isPdfExtension(extname) || fs.isBinaryExtension(extname) - ) + ); } diff --git a/lib/preview-element.js b/lib/preview-element.js index 7931102..ab4805e 100644 --- a/lib/preview-element.js +++ b/lib/preview-element.js @@ -1,50 +1,51 @@ /* @flow */ -const LINE_BREAKS_REGEX = /(?:\r\n|\r|\n)/g +const LINE_BREAKS_REGEX = /(?:\r\n|\r|\n)/g; -export default document.registerElement('textual-velocity-preview', { +export default document.registerElement("textual-velocity-preview", { prototype: Object.assign(Object.create(HTMLElement.prototype), { + attachedCallback() {}, + attributeChangedCallback() {}, + createdCallback() {}, + detachedCallback() {}, - attachedCallback () {}, - attributeChangedCallback () {}, - createdCallback () {}, - detachedCallback () {}, + updatePreview(path: string, content: string, searchRegex?: RegExp) { + this._path = path; - updatePreview (path: string, content: string, searchRegex?: RegExp) { - this._path = path - - if (typeof content === 'string') { + if (typeof content === "string") { if (searchRegex) { - const globalRegex = new RegExp(searchRegex, 'gi') - content = content.replace(globalRegex, match => `${match}`) + const globalRegex = new RegExp(searchRegex, "gi"); + content = content.replace( + globalRegex, + match => `${match}` + ); } - this.innerHTML = content.replace(LINE_BREAKS_REGEX, '
') + this.innerHTML = content.replace(LINE_BREAKS_REGEX, "
"); } }, - getTitle () { - return 'Preview (Textual Velocity)' + getTitle() { + return "Preview (Textual Velocity)"; }, - getLongTitle () { - return this.getTitle() + getLongTitle() { + return this.getTitle(); }, - getPath () { - return this._path + getPath() { + return this._path; }, - scrollToFirstHighlightedItem () { - const el = this.querySelector('span') + scrollToFirstHighlightedItem() { + const el = this.querySelector("span"); if (el) { - el.scrollIntoViewIfNeeded() + el.scrollIntoViewIfNeeded(); } }, - dispose () { - this.remove() + dispose() { + this.remove(); } - }) -}) +}); diff --git a/lib/react/app.js b/lib/react/app.js index 029d7fe..81d8357 100644 --- a/lib/react/app.js +++ b/lib/react/app.js @@ -1,17 +1,20 @@ /* @flow */ -import * as actionCreators from '../action-creators' -import {connect} from 'react-redux' -import {bindActionCreators} from 'redux' -import Main from './main' -import paginationSelector from '../reselectors/pagination' -import makeVisibleRowsSelector from '../reselectors/visible-rows' +import * as actionCreators from "../action-creators"; +import { connect } from "react-redux"; +import { bindActionCreators } from "redux"; +import Main from "./main"; +import paginationSelector from "../reselectors/pagination"; +import makeVisibleRowsSelector from "../reselectors/visible-rows"; -export default function makeApp (columns: Columns) { - const visibleRowsSelector = makeVisibleRowsSelector(columns, paginationSelector) +export default function makeApp(columns: Columns) { + const visibleRowsSelector = makeVisibleRowsSelector( + columns, + paginationSelector + ); - function mapStateToProps (state: State): MainPropsWithoutActions { - const filenames = Object.keys(state.notes) + function mapStateToProps(state: State): MainPropsWithoutActions { + const filenames = Object.keys(state.notes); return { columnHeaders: state.columnHeaders, @@ -22,24 +25,21 @@ export default function makeApp (columns: Columns) { listHeight: state.listHeight, paginationStart: paginationSelector(state).start, queryOriginal: state.queryOriginal, - readyCount: filenames - .reduce((memo, filename) => { - return state.notes[filename].ready - ? memo + 1 - : memo - }, 0), + readyCount: filenames.reduce((memo, filename) => { + return state.notes[filename].ready ? memo + 1 : memo; + }, 0), rowHeight: state.rowHeight, scrollTop: state.scrollTop, sortDirection: state.sifterResult.options.sort[0].direction, sortField: state.sifterResult.options.sort[0].field, totalCount: filenames.length, visibleRows: visibleRowsSelector(state) - } + }; } - function mapDispatchToProps (dispatch: Dispatch): MainPropsActions { - return {actions: bindActionCreators(actionCreators, dispatch)} + function mapDispatchToProps(dispatch: Dispatch): MainPropsActions { + return { actions: bindActionCreators(actionCreators, dispatch) }; } - return connect(mapStateToProps, mapDispatchToProps)(Main) + return connect(mapStateToProps, mapDispatchToProps)(Main); } diff --git a/lib/react/cell.js b/lib/react/cell.js index 0882f43..567c94b 100644 --- a/lib/react/cell.js +++ b/lib/react/cell.js @@ -1,35 +1,38 @@ /* @flow */ -import React from 'react' +import React from "react"; export default class Cell extends React.Component { props: { cell: RowCell, onDoubleClick: Function - } + }; - render () { + render() { return ( - + {this._renderContent(this.props.cell.content)} - ) + ); } - _renderContent (content: CellContent, i?: number) { - if (typeof content === 'string') { - return content + _renderContent(content: CellContent, i?: number) { + if (typeof content === "string") { + return content; } else if (content instanceof Array) { - return content.map((item, i) => this._renderContent(item, i + 1)) - } else if (typeof content === 'object') { - const attrs = content.attrs || {} + return content.map((item, i) => this._renderContent(item, i + 1)); + } else if (typeof content === "object") { + const attrs = content.attrs || {}; return ( - {content.content ? this._renderContent(content.content) : ''} + {content.content ? this._renderContent(content.content) : ""} - ) + ); } else { - return '' + return ""; } } } diff --git a/lib/react/edit-cell-str.js b/lib/react/edit-cell-str.js index 4b7ed0c..a6db0bb 100644 --- a/lib/react/edit-cell-str.js +++ b/lib/react/edit-cell-str.js @@ -1,71 +1,81 @@ /* @flow */ -import React from 'react' +import React from "react"; type EditCellStrProps = { initialVal: string, save: Function, abort: Function -} +}; export default class EditCellStr extends React.Component { state: { value: string - } + }; - props: EditCellStrProps - input: HTMLInputElement + props: EditCellStrProps; + input: HTMLInputElement; - _onKeyPress: (ev: KeyboardEvent) => void + _onKeyPress: (ev: KeyboardEvent) => void; - constructor (props: EditCellStrProps) { - super(props) - this.state = {value: props.initialVal} + constructor(props: EditCellStrProps) { + super(props); + this.state = { value: props.initialVal }; this._onKeyPress = ev => { - if (ev.keyCode === 13) { // - this._saveIfChangedOrAbort() - } else if (ev.keyCode === 27) { // - this.props.abort() + if (ev.keyCode === 13) { + // + this._saveIfChangedOrAbort(); + } else if (ev.keyCode === 27) { + // + this.props.abort(); } - } + }; } - render () { + render() { return ( - - + { - this.input = input + this.input = input; }} - value={this.state.value} onChange={this._onChange.bind(this)} onBlur={this._saveIfChangedOrAbort.bind(this)} /> + value={this.state.value} + onChange={this._onChange.bind(this)} + onBlur={this._saveIfChangedOrAbort.bind(this)} + /> - ) + ); } - componentDidMount () { - this.input.addEventListener('keydown', this._onKeyPress) - this.input.select() - this.input.focus() + componentDidMount() { + this.input.addEventListener("keydown", this._onKeyPress); + this.input.select(); + this.input.focus(); } - componentWillUnmount () { - this.input.removeEventListener('keydown', this._onKeyPress) + componentWillUnmount() { + this.input.removeEventListener("keydown", this._onKeyPress); } - _onChange (ev: any) { - this.setState({ value: ev.target.value }) + _onChange(ev: any) { + this.setState({ value: ev.target.value }); } - _saveIfChangedOrAbort () { + _saveIfChangedOrAbort() { if (this._hasChanged()) { - this.props.save(this.state.value.trim()) + this.props.save(this.state.value.trim()); } else { - this.props.abort() + this.props.abort(); } } - _hasChanged () { - return this.state.value !== this.props.initialVal && this.state.value.trim() !== this.props.initialVal + _hasChanged() { + return ( + this.state.value !== this.props.initialVal && + this.state.value.trim() !== this.props.initialVal + ); } } diff --git a/lib/react/loading-progress.js b/lib/react/loading-progress.js index 1d18f79..69b9b66 100644 --- a/lib/react/loading-progress.js +++ b/lib/react/loading-progress.js @@ -1,35 +1,42 @@ /* @flow */ -import React from 'react' +import React from "react"; export default class LoadingProgress extends React.Component { props: { readyCount: number, totalCount: number - } + }; - render () { - if (this.props.totalCount === 0 || - this.props.readyCount === 0 || - this.props.readyCount === this.props.totalCount) { - return + render() { + if ( + this.props.totalCount === 0 || + this.props.readyCount === 0 || + this.props.readyCount === this.props.totalCount + ) { + return ; } return ( -
- +
+ Reading {this.props.readyCount} of {this.props.totalCount} files - - + +
- ) + ); } - _onClick () { - atom.notifications.addInfo('Textual Velocity', { - description: 'Reading files to populate searchable note fields. It\'s only necessary for the first run of your notes path, after that it gets cached (until you change notes path or clear the cache).', + _onClick() { + atom.notifications.addInfo("Textual Velocity", { + description: + "Reading files to populate searchable note fields. It's only necessary for the first run of your notes path, after that it gets cached (until you change notes path or clear the cache).", dismissable: true - }) + }); } } diff --git a/lib/react/main.js b/lib/react/main.js index 7a393ec..a79ad01 100644 --- a/lib/react/main.js +++ b/lib/react/main.js @@ -1,134 +1,172 @@ /* @flow */ -import classNames from 'classnames' -import React from 'react' -import ReactDOM from 'react-dom' -import {Observable} from 'rxjs' -import Cell from './cell' -import EditCellStr from './edit-cell-str' -import LoadingProgress from './loading-progress' -import ResizeHandle from './resize-handle' -import ScrollableList from './scrollable-list' -import Search from './search' -import TableColumn from './table-column' +import classNames from "classnames"; +import React from "react"; +import ReactDOM from "react-dom"; +import { Observable } from "rxjs"; +import Cell from "./cell"; +import EditCellStr from "./edit-cell-str"; +import LoadingProgress from "./loading-progress"; +import ResizeHandle from "./resize-handle"; +import ScrollableList from "./scrollable-list"; +import Search from "./search"; +import TableColumn from "./table-column"; -const privates = new WeakMap() +const privates = new WeakMap(); export default class Main extends React.Component { - props: MainProps + props: MainProps; - render () { - const {actions} = this.props + render() { + const { actions } = this.props; if (!this.props.initialScanDone) { return ( -
-
- +
+
+ Scanning path… {this.props.initialScanFilesCount} files found - +
- ) + ); } - const {paginationStart, sortDirection, sortField} = this.props + const { paginationStart, sortDirection, sortField } = this.props; return ( -
- + - -
-
+ onKeyPress={actions.keyPress} + /> + +
+
{this.props.columnHeaders.map(column => { return ( - - ) + onChangeSortDirection={actions.changeSortDirection} + /> + ); })}
- + - + - {this.props.columnHeaders.map(column => ( - - + {this.props.visibleRows.map(row => { - const {filename} = row + const { filename } = row; return ( - { actions.clickRow(filename) }} - className={classNames({'is-selected': row.selected})}> + { + actions.clickRow(filename); + }} + className={classNames({ "is-selected": row.selected })} + > {row.cells.map((cell, i) => { - if (typeof cell.editCellStr === 'string') { + if (typeof cell.editCellStr === "string") { return ( - actions.editCellSave(this.props.editCellName, str)} - abort={() => actions.editCellAbort()} /> - ) + + actions.editCellSave( + this.props.editCellName, + str + )} + abort={() => actions.editCellAbort()} + /> + ); } else { return ( - { - if (cell.editCellName) { - actions.editCell(cell.editCellName) - } - }} /> - ) + { + if (cell.editCellName) { + actions.editCell(cell.editCellName); + } + }} + /> + ); } })} - ) + ); })}
- ))} + {this.props.columnHeaders.map(column => + + )}
- +
- ) + ); } - componentDidMount () { - const changeRowHeightSubscription = Observable - .fromEvent(window, 'resize') + componentDidMount() { + const changeRowHeightSubscription = Observable.fromEvent(window, "resize") .debounceTime(50) .subscribe(() => { - const el = ReactDOM.findDOMNode(this) + const el = ReactDOM.findDOMNode(this); if (el && el instanceof HTMLElement) { - const td = el.querySelector('td') + const td = el.querySelector("td"); if (td && td.clientHeight > 0) { - this.props.actions.changeRowHeight(td.clientHeight) + this.props.actions.changeRowHeight(td.clientHeight); } } - }) + }); - privates.set(this, changeRowHeightSubscription) + privates.set(this, changeRowHeightSubscription); } - componentWillUnmount () { - const changeRowHeightSubscription = privates.get(this) + componentWillUnmount() { + const changeRowHeightSubscription = privates.get(this); if (changeRowHeightSubscription) { - changeRowHeightSubscription.unsubscribe() + changeRowHeightSubscription.unsubscribe(); } - privates.delete(this) + privates.delete(this); } } diff --git a/lib/react/resize-handle.js b/lib/react/resize-handle.js index a8ce992..92f42b2 100644 --- a/lib/react/resize-handle.js +++ b/lib/react/resize-handle.js @@ -1,32 +1,34 @@ /* @flow */ -import React from 'react' -import {Observable} from 'rxjs' +import React from "react"; +import { Observable } from "rxjs"; export default class ResizeHandle extends React.Component { props: { listHeight: number, onResize: Function - } + }; - render () { + render() { return ( -
- ) +
+ ); } - _onMouseDown (mouseDownEvent: MouseEvent) { - if (mouseDownEvent.button !== 0) return // only allow to resize on left-click + _onMouseDown(mouseDownEvent: MouseEvent) { + if (mouseDownEvent.button !== 0) return; // only allow to resize on left-click - const listHeight = this.props.listHeight - const initialClientY = mouseDownEvent.clientY + const listHeight = this.props.listHeight; + const initialClientY = mouseDownEvent.clientY; - Observable - .fromEvent(document, 'mousemove') - .takeUntil(Observable.fromEvent(document, 'mouseup')) + Observable.fromEvent(document, "mousemove") + .takeUntil(Observable.fromEvent(document, "mouseup")) .subscribe(mouseMoveEvent => { - const clientYdiff = mouseMoveEvent.clientY - initialClientY - this.props.onResize(listHeight + clientYdiff) - }) + const clientYdiff = mouseMoveEvent.clientY - initialClientY; + this.props.onResize(listHeight + clientYdiff); + }); } } diff --git a/lib/react/scrollable-list.js b/lib/react/scrollable-list.js index 1ab0d47..9e86b4a 100644 --- a/lib/react/scrollable-list.js +++ b/lib/react/scrollable-list.js @@ -1,8 +1,8 @@ /* @flow */ -import React from 'react' -import ReactDOM from 'react-dom' -import {Subject} from 'rxjs' +import React from "react"; +import ReactDOM from "react-dom"; +import { Subject } from "rxjs"; type ScrollableListProps = { children?: any, @@ -12,113 +12,127 @@ type ScrollableListProps = { onScroll: Function, paginationStart: number, rowHeight: number -} +}; -const privates = new WeakMap() +const privates = new WeakMap(); export default class ScrollableList extends React.Component { state: { forcedScroll: boolean, scrolling: boolean - } + }; - props: ScrollableListProps + props: ScrollableListProps; - constructor (props: ScrollableListProps) { - super(props) + constructor(props: ScrollableListProps) { + super(props); this.state = { forcedScroll: false, scrolling: false - } + }; } - componentWillMount () { - const resetScrollingStateSubject = new Subject() + componentWillMount() { + const resetScrollingStateSubject = new Subject(); const resetScrollingStateSubscription = resetScrollingStateSubject .debounceTime(400) .subscribe(() => { - this.setState({scrolling: false}) - }) + this.setState({ scrolling: false }); + }); - const resetForcedScrollSubject = new Subject() + const resetForcedScrollSubject = new Subject(); const resetForcedScrollSubscription = resetForcedScrollSubject .debounceTime(100) .subscribe(() => { - this.setState({forcedScroll: false}) - }) + this.setState({ forcedScroll: false }); + }); privates.set(this, { resetScrollingStateSubject, resetScrollingStateSubscription, resetForcedScrollSubject, resetForcedScrollSubscription - }) + }); } - render () { + render() { return ( -
-
-
+
+
+
{this.props.children}
- ) + ); } - componentWillReceiveProps (nextProps: ScrollableListProps) { + componentWillReceiveProps(nextProps: ScrollableListProps) { // If next scrollTop value doesn't match current it means it's a "forced scroll" from somewhere else // So indicate this to avoid pushing additional scroll // Will be reset after the debounce - const el = ReactDOM.findDOMNode(this) + const el = ReactDOM.findDOMNode(this); if (el && el.scrollTop !== nextProps.scrollTop) { - this.setState({forcedScroll: true}) + this.setState({ forcedScroll: true }); - const {resetForcedScrollSubject} = privates.get(this) || {} - resetForcedScrollSubject.next() + const { resetForcedScrollSubject } = privates.get(this) || {}; + resetForcedScrollSubject.next(); } } - componentDidUpdate () { - const el = ReactDOM.findDOMNode(this) - if (!this.state.scrolling && el && el instanceof HTMLElement && el.scrollTop !== this.props.scrollTop) { - el.scrollTop = this.props.scrollTop + componentDidUpdate() { + const el = ReactDOM.findDOMNode(this); + if ( + !this.state.scrolling && + el && + el instanceof HTMLElement && + el.scrollTop !== this.props.scrollTop + ) { + el.scrollTop = this.props.scrollTop; } } - compomentWillUnmount () { - const {resetForcedScrollSubscription, resetScrollingStateSubscription} = privates.get(this) || {} + compomentWillUnmount() { + const { resetForcedScrollSubscription, resetScrollingStateSubscription } = + privates.get(this) || {}; - resetForcedScrollSubscription.unsubscribe() - resetScrollingStateSubscription.unsubscribe() + resetForcedScrollSubscription.unsubscribe(); + resetScrollingStateSubscription.unsubscribe(); - privates.delete(this) + privates.delete(this); } - _onScroll (ev: any) { + _onScroll(ev: any) { if (!this.state.forcedScroll) { if (!this.state.scrolling) { - this.setState({scrolling: true}) + this.setState({ scrolling: true }); } - this.props.onScroll(ev.target.scrollTop) + this.props.onScroll(ev.target.scrollTop); - const {resetScrollingStateSubject} = privates.get(this) || {} - resetScrollingStateSubject.next() + const { resetScrollingStateSubject } = privates.get(this) || {}; + resetScrollingStateSubject.next(); } } } diff --git a/lib/react/search.js b/lib/react/search.js index eb0e001..0cc69c9 100644 --- a/lib/react/search.js +++ b/lib/react/search.js @@ -1,13 +1,13 @@ /* @flow */ -import Disposables from '../disposables' -import React from 'react' -import ReactDOM from 'react-dom' -import {Subject} from 'rxjs' +import Disposables from "../disposables"; +import React from "react"; +import ReactDOM from "react-dom"; +import { Subject } from "rxjs"; -const IS_INPUT_ELEMENT_REGEX = /INPUT|ATOM-TEXT-EDITOR/ +const IS_INPUT_ELEMENT_REGEX = /INPUT|ATOM-TEXT-EDITOR/; -const privates = new WeakMap() +const privates = new WeakMap(); export default class Search extends React.Component { props: { @@ -15,80 +15,99 @@ export default class Search extends React.Component { query: string, onKeyPress: Function, onSearch: Function - } + }; - componentWillMount () { - const focusInputSubject = new Subject() + componentWillMount() { + const focusInputSubject = new Subject(); const focusInputSubscription = focusInputSubject .debounceTime(50) .subscribe((force: boolean) => { - const shouldFocus = force || + const shouldFocus = + force || (this.props.focusOnEvents && - document.activeElement && - !IS_INPUT_ELEMENT_REGEX.test(document.activeElement.tagName)) + document.activeElement && + !IS_INPUT_ELEMENT_REGEX.test(document.activeElement.tagName)); if (shouldFocus) { - const el = ReactDOM.findDOMNode(this) - if (el && el instanceof HTMLInputElement && el !== document.activeElement) { - el.select() - el.focus() + const el = ReactDOM.findDOMNode(this); + if ( + el && + el instanceof HTMLInputElement && + el !== document.activeElement + ) { + el.select(); + el.focus(); } } - }) + }); privates.set(this, { focusInputSubject, focusInputSubscription, disposables: new Disposables(), onKeyPress: (ev: KeyboardEvent) => this.props.onKeyPress(ev) - }) + }); } - render () { + render() { return ( - this.props.onSearch(ev.target.value)} /> - ) + className="tv-input tv-input--search native-key-bindings" + placeholder="Search, or press enter to create a new untitled file" + onChange={ev => this.props.onSearch(ev.target.value)} + /> + ); } - componentDidMount () { - const {disposables, focusInputSubject, onKeyPress} = privates.get(this) || {} + componentDidMount() { + const { disposables, focusInputSubject, onKeyPress } = + privates.get(this) || {}; - focusInputSubject.next(false) + focusInputSubject.next(false); disposables.add( - atom.commands.add('atom-workspace', 'textual-velocity:focus-on-search', () => focusInputSubject.next(true)), - atom.commands.add('atom-workspace', 'textual-velocity:toggle-atom-window', () => focusInputSubject.next(true)), - atom.commands.add('atom-workspace', 'textual-velocity:toggle-panel', () => focusInputSubject.next(true)) - ) + atom.commands.add( + "atom-workspace", + "textual-velocity:focus-on-search", + () => focusInputSubject.next(true) + ), + atom.commands.add( + "atom-workspace", + "textual-velocity:toggle-atom-window", + () => focusInputSubject.next(true) + ), + atom.commands.add("atom-workspace", "textual-velocity:toggle-panel", () => + focusInputSubject.next(true) + ) + ); // An evil necessary, due to https://github.com/atom/atom/blob/8eaaf40a2cffd9e091a420ca0634c9da9cf4b544/src/window-event-handler.coffee#L77 // that prevents keydown events to propagate to React's event handler - const el = ReactDOM.findDOMNode(this) + const el = ReactDOM.findDOMNode(this); if (el && el instanceof HTMLElement) { - el.addEventListener('keydown', onKeyPress) + el.addEventListener("keydown", onKeyPress); } } - componentDidUpdate () { - const {focusInputSubject} = privates.get(this) || {} - focusInputSubject.next(false) + componentDidUpdate() { + const { focusInputSubject } = privates.get(this) || {}; + focusInputSubject.next(false); } - componentWillUnmount () { - const {disposables, onKeyPress, focusInputSubscription} = privates.get(this) || {} + componentWillUnmount() { + const { disposables, onKeyPress, focusInputSubscription } = + privates.get(this) || {}; - focusInputSubscription.unsubscribe() - disposables.dispose() + focusInputSubscription.unsubscribe(); + disposables.dispose(); - const el = ReactDOM.findDOMNode(this) + const el = ReactDOM.findDOMNode(this); if (el && el instanceof HTMLElement) { - el.removeEventListener('keydown', onKeyPress) + el.removeEventListener("keydown", onKeyPress); } - privates.delete(this) + privates.delete(this); } } diff --git a/lib/react/table-column.js b/lib/react/table-column.js index 67ce84c..a9002ed 100644 --- a/lib/react/table-column.js +++ b/lib/react/table-column.js @@ -1,7 +1,7 @@ /* @flow */ -import React from 'react' -import classNames from 'classnames' +import React from "react"; +import classNames from "classnames"; export default class TableColumn extends React.Component { props: { @@ -10,36 +10,41 @@ export default class TableColumn extends React.Component { sortDirection: SortDirection, onSortByField: Function, onChangeSortDirection: Function - } + }; - render () { - const c = this.props.column + render() { + const c = this.props.column; return ( - + {c.title} {this._sortIndicator()} - ) + ); } - _sortIndicator () { + _sortIndicator() { if (this.props.isSelected) { return ( - - ) + + ); } } - _onClick () { + _onClick() { if (this.props.isSelected) { - const direction = this.props.sortDirection === 'asc' ? 'desc' : 'asc' - this.props.onChangeSortDirection(direction) + const direction = this.props.sortDirection === "asc" ? "desc" : "asc"; + this.props.onChangeSortDirection(direction); } else { - this.props.onSortByField(this.props.column.sortField) + this.props.onSortByField(this.props.column.sortField); } } } diff --git a/lib/reducers/column-headers.js b/lib/reducers/column-headers.js index 70cb15c..804d95f 100644 --- a/lib/reducers/column-headers.js +++ b/lib/reducers/column-headers.js @@ -1,23 +1,26 @@ /* @flow */ -import {name} from '../columns' -import * as A from '../action-creators' +import { name } from "../columns"; +import * as A from "../action-creators"; -export default function makeColumnHeadersReducer (columns: Columns) { +export default function makeColumnHeadersReducer(columns: Columns) { const defaults = columns.map(c => ({ sortField: c.sortField, title: c.title, width: c.width - })) + })); - return function columnHeadersReducer (state: Array = defaults, action: Action) { + return function columnHeadersReducer( + state: Array = defaults, + action: Action + ) { switch (action.type) { case A.CHANGED_HIDDEN_COLUMNS: - const hiddenColumns = action.hiddenColumns - return defaults.filter(column => !hiddenColumns.includes(name(column))) + const hiddenColumns = action.hiddenColumns; + return defaults.filter(column => !hiddenColumns.includes(name(column))); default: - return state + return state; } - } + }; } diff --git a/lib/reducers/edit-cell-name.js b/lib/reducers/edit-cell-name.js index be06243..24d53d7 100644 --- a/lib/reducers/edit-cell-name.js +++ b/lib/reducers/edit-cell-name.js @@ -1,24 +1,27 @@ /* @flow */ -import * as A from '../action-creators' +import * as A from "../action-creators"; -export default function makeEditCellNameReducer (columns: Columns) { - return function editCellNameReducer (state: EditCellName = null, action: Action) { +export default function makeEditCellNameReducer(columns: Columns) { + return function editCellNameReducer( + state: EditCellName = null, + action: Action + ) { switch (action.type) { case A.EDIT_CELL: - const editCellName = action.name + const editCellName = action.name; return columns.some(column => column.editCellName === editCellName) ? editCellName - : null + : null; case A.EDIT_CELL_ABORT: - return null + return null; case A.EDIT_CELL_SAVE: - return null + return null; default: - return state + return state; } - } + }; } diff --git a/lib/reducers/index.js b/lib/reducers/index.js index ec72854..a07105f 100644 --- a/lib/reducers/index.js +++ b/lib/reducers/index.js @@ -1,23 +1,27 @@ /* @flow */ -import initialScan from './initial-scan' -import listHeight from './list-height' -import makeColumnHeaders from './column-headers' -import makeEditCellName from './edit-cell-name' -import makeNotes from './notes' -import makeSifterResult from './sifter-result' -import queryOriginal from './query-original' -import rowHeight from './row-height' -import scrollTop from './scroll-top' -import selectedNote from './selected-note' +import initialScan from "./initial-scan"; +import listHeight from "./list-height"; +import makeColumnHeaders from "./column-headers"; +import makeEditCellName from "./edit-cell-name"; +import makeNotes from "./notes"; +import makeSifterResult from "./sifter-result"; +import queryOriginal from "./query-original"; +import rowHeight from "./row-height"; +import scrollTop from "./scroll-top"; +import selectedNote from "./selected-note"; -export default function makeRoot (columns: Columns, fileReaders: FileReaders, noteFields: NoteFields) { - const columnHeaders = makeColumnHeaders(columns) - const editCellName = makeEditCellName(columns) - const notes = makeNotes(fileReaders, noteFields) - const sifterResult = makeSifterResult(noteFields) +export default function makeRoot( + columns: Columns, + fileReaders: FileReaders, + noteFields: NoteFields +) { + const columnHeaders = makeColumnHeaders(columns); + const editCellName = makeEditCellName(columns); + const notes = makeNotes(fileReaders, noteFields); + const sifterResult = makeSifterResult(noteFields); - return function root (state: State, action: Action) { + return function root(state: State, action: Action) { const next: State = { columnHeaders: columnHeaders(state.columnHeaders, action), dir: state.dir, @@ -30,14 +34,25 @@ export default function makeRoot (columns: Columns, fileReaders: FileReaders, no scrollTop: state.scrollTop, selectedNote: state.selectedNote, sifterResult: state.sifterResult - } + }; - next.sifterResult = sifterResult(state.sifterResult, action, next.notes) + next.sifterResult = sifterResult(state.sifterResult, action, next.notes); if (next.initialScan.done) { - next.selectedNote = selectedNote(state.selectedNote, action, next.dir, next.sifterResult) + next.selectedNote = selectedNote( + state.selectedNote, + action, + next.dir, + next.sifterResult + ); } - next.scrollTop = scrollTop(state.scrollTop, action, next.listHeight, next.rowHeight, next.selectedNote) + next.scrollTop = scrollTop( + state.scrollTop, + action, + next.listHeight, + next.rowHeight, + next.selectedNote + ); - return next - } + return next; + }; } diff --git a/lib/reducers/initial-scan.js b/lib/reducers/initial-scan.js index ec26034..06f7fdb 100644 --- a/lib/reducers/initial-scan.js +++ b/lib/reducers/initial-scan.js @@ -1,15 +1,18 @@ /* @flow */ -import * as A from '../action-creators' +import * as A from "../action-creators"; const defaults = { done: false, rawFiles: [] -} +}; -export default function initialScanReducer (state: InitialScan = defaults, action: Action) { +export default function initialScanReducer( + state: InitialScan = defaults, + action: Action +) { if (state.done) { - return state + return state; } switch (action.type) { @@ -17,21 +20,21 @@ export default function initialScanReducer (state: InitialScan = defaults, actio return { ...state, rawFiles: state.rawFiles.concat(action.rawFile) - } + }; case A.INITIAL_SCAN_DONE: return { ...state, done: true - } + }; case A.INITIAL_SCAN_RAW_FILES_READ: return { ...state, rawFiles: [] - } + }; default: - return state + return state; } } diff --git a/lib/reducers/list-height.js b/lib/reducers/list-height.js index a9abde0..905a430 100644 --- a/lib/reducers/list-height.js +++ b/lib/reducers/list-height.js @@ -1,14 +1,14 @@ /* @flow */ -import * as A from '../action-creators' +import * as A from "../action-creators"; -export default function listHeightReducer (state: number = 0, action: Action) { +export default function listHeightReducer(state: number = 0, action: Action) { switch (action.type) { case A.RESIZED_LIST: case A.CHANGED_LIST_HEIGHT: - return action.listHeight + return action.listHeight; default: - return state + return state; } } diff --git a/lib/reducers/notes.js b/lib/reducers/notes.js index c1ab6d2..d88af99 100644 --- a/lib/reducers/notes.js +++ b/lib/reducers/notes.js @@ -1,83 +1,88 @@ /* @flow */ -import * as A from '../action-creators' +import * as A from "../action-creators"; -export default function makeNotesReducer (fileReaders: FileReaders, noteFields: NoteFields) { +export default function makeNotesReducer( + fileReaders: FileReaders, + noteFields: NoteFields +) { const noteWithUpdatedFields = (note: Note, filename: string) => { noteFields.forEach(noteField => { if (noteField.value) { - note[noteField.notePropName] = noteField.value(note, filename) + note[noteField.notePropName] = noteField.value(note, filename); } - }) + }); - note.ready = fileReaders.every(fileReader => note[fileReader.notePropName] !== undefined) + note.ready = fileReaders.every( + fileReader => note[fileReader.notePropName] !== undefined + ); - return note - } + return note; + }; - function newNote (rawFile: RawFile): Note { + function newNote(rawFile: RawFile): Note { const note = { id: process.hrtime().toString(), stats: rawFile.stats, ready: false, // these are set by noteFields later: - ext: '', - name: '' - } - return noteWithUpdatedFields(note, rawFile.filename) + ext: "", + name: "" + }; + return noteWithUpdatedFields(note, rawFile.filename); } - return function nextNotesReducer (state: Notes = {}, action: Action, initialScan: InitialScan) { + return function nextNotesReducer( + state: Notes = {}, + action: Action, + initialScan: InitialScan + ) { switch (action.type) { case A.INITIAL_SCAN_DONE: - return initialScan.rawFiles - .reduce((nextNotes, rawFile) => { - const note = state[rawFile.filename] || newNote(rawFile) - nextNotes[rawFile.filename] = noteWithUpdatedFields(note, rawFile.filename) - return nextNotes - }, {}) + return initialScan.rawFiles.reduce((nextNotes, rawFile) => { + const note = state[rawFile.filename] || newNote(rawFile); + nextNotes[rawFile.filename] = noteWithUpdatedFields( + note, + rawFile.filename + ); + return nextNotes; + }, {}); case A.FILE_ADDED: - if (!initialScan.done) return state + if (!initialScan.done) return state; return { ...state, [action.rawFile.filename]: newNote(action.rawFile) - } + }; case A.FILE_READ: - const { - filename: readFilename, - notePropName, - value - } = action + const { filename: readFilename, notePropName, value } = action; - return Object.keys(state) - .reduce((nextNotes, filename) => { - if (filename === readFilename) { - const note = { - ...state[filename], - [notePropName]: value - } - nextNotes[filename] = noteWithUpdatedFields(note, filename) - } else { - nextNotes[filename] = state[filename] - } - return nextNotes - }, {}) + return Object.keys(state).reduce((nextNotes, filename) => { + if (filename === readFilename) { + const note = { + ...state[filename], + [notePropName]: value + }; + nextNotes[filename] = noteWithUpdatedFields(note, filename); + } else { + nextNotes[filename] = state[filename]; + } + return nextNotes; + }, {}); case A.FILE_DELETED: - const deletedFilename = action.filename - return Object.keys(state) - .reduce((nextNotes, filename) => { - if (filename !== deletedFilename) { - nextNotes[filename] = state[filename] - } - return nextNotes - }, {}) + const deletedFilename = action.filename; + return Object.keys(state).reduce((nextNotes, filename) => { + if (filename !== deletedFilename) { + nextNotes[filename] = state[filename]; + } + return nextNotes; + }, {}); default: - return state + return state; } - } + }; } diff --git a/lib/reducers/query-original.js b/lib/reducers/query-original.js index accba9f..4abcd79 100644 --- a/lib/reducers/query-original.js +++ b/lib/reducers/query-original.js @@ -1,16 +1,16 @@ /* @flow */ -import * as A from '../action-creators' +import * as A from "../action-creators"; -export default function queryOriginal (state: string = '', action: Action) { +export default function queryOriginal(state: string = "", action: Action) { switch (action.type) { case A.RESET_SEARCH: - return '' + return ""; case A.SEARCH: - return action.query + return action.query; default: - return state + return state; } } diff --git a/lib/reducers/row-height.js b/lib/reducers/row-height.js index 6bde918..a8492ad 100644 --- a/lib/reducers/row-height.js +++ b/lib/reducers/row-height.js @@ -1,13 +1,13 @@ /* @flow */ -import * as A from '../action-creators' +import * as A from "../action-creators"; -export default function rowHeightReducer (state: number = 25, action: Action) { +export default function rowHeightReducer(state: number = 25, action: Action) { switch (action.type) { case A.CHANGED_ROW_HEIGHT: - return action.rowHeight + return action.rowHeight; default: - return state + return state; } } diff --git a/lib/reducers/scroll-top.js b/lib/reducers/scroll-top.js index 0a77e61..b6e2b22 100644 --- a/lib/reducers/scroll-top.js +++ b/lib/reducers/scroll-top.js @@ -1,38 +1,44 @@ /* @flow */ -import * as A from '../action-creators' +import * as A from "../action-creators"; -export default function scrollTop (state: number = 0, action: Action, listHeight: number, rowHeight: number, nextSelectedNote: ?SelectedNote) { +export default function scrollTop( + state: number = 0, + action: Action, + listHeight: number, + rowHeight: number, + nextSelectedNote: ?SelectedNote +) { switch (action.type) { case A.SCROLLED: - return action.scrollTop + return action.scrollTop; case A.RESET_SEARCH: case A.SEARCH: - return 0 + return 0; case A.CHANGED_ACTIVE_PANE_ITEM: case A.SELECT_NEXT: case A.SELECT_PREV: if (nextSelectedNote) { - const selectedScrollTop = nextSelectedNote.index * rowHeight + const selectedScrollTop = nextSelectedNote.index * rowHeight; if (state + listHeight < selectedScrollTop + rowHeight) { // selected file X is located after the visible bounds // from: ..[...]..X.. // to: ......[..X]. - return selectedScrollTop + rowHeight - listHeight + return selectedScrollTop + rowHeight - listHeight; } else if (state > selectedScrollTop) { // selected file X is located before the visible bounds // from: ..X..[...].. // to: .[X..]...... - return selectedScrollTop + return selectedScrollTop; } } - return state // no selection -or- selection is within visible viewport + return state; // no selection -or- selection is within visible viewport default: - return state + return state; } } diff --git a/lib/reducers/selected-note.js b/lib/reducers/selected-note.js index 9aa3f82..ea4db9b 100644 --- a/lib/reducers/selected-note.js +++ b/lib/reducers/selected-note.js @@ -1,67 +1,68 @@ /* @flow */ -import Path from 'path' -import * as A from '../action-creators' +import Path from "path"; +import * as A from "../action-creators"; -export default function selectedNoteReducer (state: ?SelectedNote = null, action: Action, dir: string, nextSifterResult: SifterResult) { - if (nextSifterResult.items.length === 0) return null +export default function selectedNoteReducer( + state: ?SelectedNote = null, + action: Action, + dir: string, + nextSifterResult: SifterResult +) { + if (nextSifterResult.items.length === 0) return null; - let filename - let index = -1 + let filename; + let index = -1; switch (action.type) { case A.SEARCH: case A.RESET_SEARCH: - return null + return null; case A.CHANGED_ACTIVE_PANE_ITEM: - if (!action.path) return null - filename = action.path.replace(dir + Path.sep, '') - return getSelectedNote(nextSifterResult, filename) + if (!action.path) return null; + filename = action.path.replace(dir + Path.sep, ""); + return getSelectedNote(nextSifterResult, filename); case A.CLICK_ROW: - return getSelectedNote(nextSifterResult, action.filename) + return getSelectedNote(nextSifterResult, action.filename); case A.CHANGED_SORT_FIELD: case A.CHANGED_SORT_DIRECTION: - return getSelectedNote(nextSifterResult, state && state.filename) + return getSelectedNote(nextSifterResult, state && state.filename); case A.SELECT_NEXT: - index = findIndex(nextSifterResult, state && state.filename) + index = findIndex(nextSifterResult, state && state.filename); index = index === -1 ? 0 // start at beginning of list - : Math.min(index + 1, nextSifterResult.items.length - 1) // next -or- stop at end of list - return {index, filename: nextSifterResult.items[index].id} + : Math.min(index + 1, nextSifterResult.items.length - 1); // next -or- stop at end of list + return { index, filename: nextSifterResult.items[index].id }; case A.SELECT_PREV: - index = findIndex(nextSifterResult, state && state.filename) + index = findIndex(nextSifterResult, state && state.filename); index = index === -1 ? nextSifterResult.items.length - 1 // start at end of list - : Math.max(index - 1, 0) // prev -or- stop at beginning of list - return {index, filename: nextSifterResult.items[index].id} + : Math.max(index - 1, 0); // prev -or- stop at beginning of list + return { index, filename: nextSifterResult.items[index].id }; case A.FILE_DELETED: - return state && state.filename === action.filename - ? null - : state + return state && state.filename === action.filename ? null : state; case A.FILE_ADDED: - return getSelectedNote(nextSifterResult, action.rawFile.filename) + return getSelectedNote(nextSifterResult, action.rawFile.filename); default: - return state + return state; } } -function getSelectedNote (sifterResult: SifterResult, filename: ?string) { - const index = findIndex(sifterResult, filename) - return filename && index >= 0 - ? {filename, index} - : null +function getSelectedNote(sifterResult: SifterResult, filename: ?string) { + const index = findIndex(sifterResult, filename); + return filename && index >= 0 ? { filename, index } : null; } -function findIndex (sifterResult: SifterResult, filename: ?string) { +function findIndex(sifterResult: SifterResult, filename: ?string) { return filename ? sifterResult.items.findIndex(item => item.id === filename) - : -1 + : -1; } diff --git a/lib/reducers/sifter-result.js b/lib/reducers/sifter-result.js index df9d895..f709b96 100644 --- a/lib/reducers/sifter-result.js +++ b/lib/reducers/sifter-result.js @@ -1,59 +1,61 @@ /* @flow */ -import Sifter from 'sifter' -import * as A from '../action-creators' +import Sifter from "sifter"; +import * as A from "../action-creators"; -export default function makeSifterResultReducer (noteFields: NoteFields) { - const sifter = new Sifter() +export default function makeSifterResultReducer(noteFields: NoteFields) { + const sifter = new Sifter(); - const secondarySort = {field: '$score', direction: 'desc'} + const secondarySort = { field: "$score", direction: "desc" }; const defaults = { items: [], options: { fields: [], sort: [secondarySort] }, - query: '', + query: "", tokens: [], total: 0 - } + }; - return function sifterResultReducer (state: SifterResult = defaults, action: Action, nextNotes: Notes) { - let query, direction, field + return function sifterResultReducer( + state: SifterResult = defaults, + action: Action, + nextNotes: Notes + ) { + let query, direction, field; switch (action.type) { case A.SEARCH: - query = action.query - break + query = action.query; + break; case A.FILE_ADDED: case A.FILE_CHANGED: case A.FILE_DELETED: case A.FILE_READ: - break + break; case A.CHANGED_SORT_FIELD: - field = action.sortField - break + field = action.sortField; + break; case A.CHANGED_SORT_DIRECTION: - direction = action.sortDirection - break + direction = action.sortDirection; + break; case A.RESET_SEARCH: case A.INITIAL_SCAN_DONE: case A.INITIAL_SCAN_RAW_FILES_READ: - query = '' - break + query = ""; + break; default: - return state + return state; } - sifter.items = nextNotes // use notes as items to be search - query = query !== undefined - ? query - : state.query + sifter.items = nextNotes; // use notes as items to be search + query = query !== undefined ? query : state.query; return sifter.search(query, { fields: noteFields.map(noteField => noteField.notePropName), @@ -64,7 +66,7 @@ export default function makeSifterResultReducer (noteFields: NoteFields) { }, secondarySort ], - conjunction: 'and' - }) - } + conjunction: "and" + }); + }; } diff --git a/lib/reselectors/pagination.js b/lib/reselectors/pagination.js index bbdfdb6..84c9e8a 100644 --- a/lib/reselectors/pagination.js +++ b/lib/reselectors/pagination.js @@ -1,12 +1,12 @@ /* @flow */ -import {createSelector} from 'reselect' +import { createSelector } from "reselect"; -const getListHeight = (state: State) => state.listHeight -const getRowHeight = (state: State) => state.rowHeight -const getScrollTop = (state: State) => state.scrollTop +const getListHeight = (state: State) => state.listHeight; +const getRowHeight = (state: State) => state.rowHeight; +const getScrollTop = (state: State) => state.scrollTop; -const VISIBLE_PADDING = 2 +const VISIBLE_PADDING = 2; export default createSelector( getListHeight, @@ -16,6 +16,6 @@ export default createSelector( return { start: (scrollTop / rowHeight) | 0, limit: ((listHeight / rowHeight) | 0) + VISIBLE_PADDING - } + }; } -) +); diff --git a/lib/reselectors/visible-rows.js b/lib/reselectors/visible-rows.js index efdfc0a..b42b21c 100644 --- a/lib/reselectors/visible-rows.js +++ b/lib/reselectors/visible-rows.js @@ -1,18 +1,21 @@ /* @flow */ -import Path from 'path' -import {createSelector} from 'reselect' -import SearchMatch from '../search-match' -import {name} from '../columns' +import Path from "path"; +import { createSelector } from "reselect"; +import SearchMatch from "../search-match"; +import { name } from "../columns"; -const getDir = (state: State) => state.dir -const getEditCellName = (state: State) => state.editCellName -const getNotes = (state: State) => state.notes -const getSelectedNote = (state: State) => state.selectedNote -const getSifterResult = (state: State) => state.sifterResult -const getColumnHeadersLen = (state: State) => state.columnHeaders.length +const getDir = (state: State) => state.dir; +const getEditCellName = (state: State) => state.editCellName; +const getNotes = (state: State) => state.notes; +const getSelectedNote = (state: State) => state.selectedNote; +const getSifterResult = (state: State) => state.sifterResult; +const getColumnHeadersLen = (state: State) => state.columnHeaders.length; -export default function makeVisibleRowsSelector (columns: Columns, paginationSelector: (state: State) => Pagination) { +export default function makeVisibleRowsSelector( + columns: Columns, + paginationSelector: (state: State) => Pagination +) { return createSelector( getDir, getEditCellName, @@ -21,22 +24,29 @@ export default function makeVisibleRowsSelector (columns: Columns, paginationSel getSelectedNote, getSifterResult, getColumnHeadersLen, - (dir: string, editCellName: EditCellName, notes: Notes, pagination: Pagination, selectedNote: ?SelectedNote, sifterResult: SifterResult) => { - const token = sifterResult.tokens[0] - const selectedFilename = selectedNote && selectedNote.filename - const hiddenColumns = atom.config.get('textual-velocity.hiddenColumns') + ( + dir: string, + editCellName: EditCellName, + notes: Notes, + pagination: Pagination, + selectedNote: ?SelectedNote, + sifterResult: SifterResult + ) => { + const token = sifterResult.tokens[0]; + const selectedFilename = selectedNote && selectedNote.filename; + const hiddenColumns = atom.config.get("textual-velocity.hiddenColumns"); return sifterResult.items .slice(pagination.start, pagination.start + pagination.limit) .map((item, i) => { - const filename = item.id - const note = notes[filename] + const filename = item.id; + const note = notes[filename]; const cellContentParams = { note, path: Path.join(dir, filename), searchMatch: token && new SearchMatch(token.regex) - } - const selected = filename === selectedFilename + }; + const selected = filename === selectedFilename; return { id: note.id, @@ -47,18 +57,19 @@ export default function makeVisibleRowsSelector (columns: Columns, paginationSel .map(column => { if (selected && editCellName === column.editCellName) { return { - editCellStr: (column.editCellStr && column.editCellStr(note)) || '' - } + editCellStr: + (column.editCellStr && column.editCellStr(note)) || "" + }; } else { return { - className: column.className || '', + className: column.className || "", content: column.cellContent(cellContentParams), editCellName: column.editCellName - } + }; } }) - } - }) + }; + }); } - ) + ); } diff --git a/lib/restart-session-for-new-config-to-take-effect.js b/lib/restart-session-for-new-config-to-take-effect.js index c0774a9..1a10c74 100644 --- a/lib/restart-session-for-new-config-to-take-effect.js +++ b/lib/restart-session-for-new-config-to-take-effect.js @@ -1,53 +1,84 @@ /* @flow */ -import Disposables from './disposables' +import Disposables from "./disposables"; -const privates = new WeakMap() +const privates = new WeakMap(); export default class RestartSessionForNewConfigToTakeEffect { - constructor () { - const title = 'Textual Velocity' + constructor() { + const title = "Textual Velocity"; const createNotificationHandler = (desc: string) => () => { - for (let notification of atom.notifications.getNotifications().reverse()) { + for (let notification of atom.notifications + .getNotifications() + .reverse()) { if (notification.isDismissed()) { - break // stop when reaching a dismissed notification + break; // stop when reaching a dismissed notification } - const opts = notification.getOptions() + const opts = notification.getOptions(); if (opts && opts.description === desc && !notification.isDismissed()) { - return false // there is already a notification of this desc, don't open a new one + return false; // there is already a notification of this desc, don't open a new one } } atom.notifications.addInfo(title, { - buttons: [{ - text: 'Restart session', - onDidClick: () => { - for (let notification of atom.notifications.getNotifications().reverse()) { - if (notification.isDismissed()) { - break // stop when reaching a dismissed notification - } - const opts = notification.getOptions() - if (opts && opts.description === desc && !notification.isDismissed()) { - notification.dismiss() + buttons: [ + { + text: "Restart session", + onDidClick: () => { + for (let notification of atom.notifications + .getNotifications() + .reverse()) { + if (notification.isDismissed()) { + break; // stop when reaching a dismissed notification + } + const opts = notification.getOptions(); + if ( + opts && + opts.description === desc && + !notification.isDismissed() + ) { + notification.dismiss(); + } } + atom.commands.dispatch( + atom.views.getView(atom.workspace), + "textual-velocity:restart-session" + ); } - atom.commands.dispatch(atom.views.getView(atom.workspace), 'textual-velocity:restart-session') } - }], + ], description: desc, dismissable: true - }) - } + }); + }; - privates.set(this, new Disposables( - atom.config.onDidChange('textual-velocity.path', createNotificationHandler('Restart session to load notes from the new notes path')), - atom.config.onDidChange('textual-velocity.ignoredNames', createNotificationHandler('Restart session to ignore/unignore notes by the changed ignored names')), - atom.config.onDidChange('textual-velocity.excludeVcsIgnoredPaths', createNotificationHandler('Restart session to exclude VCS ignored paths')) - )) + privates.set( + this, + new Disposables( + atom.config.onDidChange( + "textual-velocity.path", + createNotificationHandler( + "Restart session to load notes from the new notes path" + ) + ), + atom.config.onDidChange( + "textual-velocity.ignoredNames", + createNotificationHandler( + "Restart session to ignore/unignore notes by the changed ignored names" + ) + ), + atom.config.onDidChange( + "textual-velocity.excludeVcsIgnoredPaths", + createNotificationHandler( + "Restart session to exclude VCS ignored paths" + ) + ) + ) + ); } - dispose () { - const disposable = privates.get(this) || {} - disposable.dispose() + dispose() { + const disposable = privates.get(this) || {}; + disposable.dispose(); } } diff --git a/lib/search-match.js b/lib/search-match.js index b767e94..9b233b5 100644 --- a/lib/search-match.js +++ b/lib/search-match.js @@ -1,23 +1,23 @@ /* @flow */ export default class SearchMatch { - _regex: RegExp + _regex: RegExp; - constructor (regex: RegExp) { - this._regex = regex + constructor(regex: RegExp) { + this._regex = regex; } - content (str: string): SearchMatchContent | void { - const m = this._regex.exec(str) - if (!m) return + content(str: string): SearchMatchContent | void { + const m = this._regex.exec(str); + if (!m) return; return [ str.slice(0, m.index), { - attrs: {className: 'text-highlight'}, + attrs: { className: "text-highlight" }, content: m[0] }, str.slice(m.index + m[0].length) - ] + ]; } } diff --git a/lib/service-consumers/defaults.js b/lib/service-consumers/defaults.js index 40c8934..3a3528b 100644 --- a/lib/service-consumers/defaults.js +++ b/lib/service-consumers/defaults.js @@ -1,49 +1,63 @@ /* @flow */ -import Disposables from '../disposables' -import FileIconColumn from '../columns/file-icon-column' -import StatsDateColumn from '../columns/stats-date-column' -import SummaryColumn from '../columns/summary-column' -import statsFileReader from '../file-readers/stats-file-reader' -import contentFileReader from '../file-readers/content-file-reader' -import StatsDateField from '../fields/stats-date-field' -import ParsedPathField from '../fields/parsed-path-field' +import Disposables from "../disposables"; +import FileIconColumn from "../columns/file-icon-column"; +import StatsDateColumn from "../columns/stats-date-column"; +import SummaryColumn from "../columns/summary-column"; +import statsFileReader from "../file-readers/stats-file-reader"; +import contentFileReader from "../file-readers/content-file-reader"; +import StatsDateField from "../fields/stats-date-field"; +import ParsedPathField from "../fields/parsed-path-field"; -const NAME_FIELD = 'name' -const EXT_FIELD = 'ext' -const LAST_UPDATE_FIELD = 'lastupdate' -const BIRTHTIME_FIELD = 'birthtime' +const NAME_FIELD = "name"; +const EXT_FIELD = "ext"; +const LAST_UPDATE_FIELD = "lastupdate"; +const BIRTHTIME_FIELD = "birthtime"; export default { - - consumeService (service: Service, summaryEditCellName: string) { - service.registerFileReaders(statsFileReader, contentFileReader) + consumeService(service: Service, summaryEditCellName: string) { + service.registerFileReaders(statsFileReader, contentFileReader); service.registerFields( contentFileReader, - new ParsedPathField({notePropName: NAME_FIELD, parsedPathPropName: 'name'}), - new ParsedPathField({notePropName: EXT_FIELD, parsedPathPropName: 'ext'}), - new StatsDateField({notePropName: LAST_UPDATE_FIELD, statsPropName: 'mtime'}), - new StatsDateField({notePropName: BIRTHTIME_FIELD, statsPropName: 'birthtime'}) - ) + new ParsedPathField({ + notePropName: NAME_FIELD, + parsedPathPropName: "name" + }), + new ParsedPathField({ + notePropName: EXT_FIELD, + parsedPathPropName: "ext" + }), + new StatsDateField({ + notePropName: LAST_UPDATE_FIELD, + statsPropName: "mtime" + }), + new StatsDateField({ + notePropName: BIRTHTIME_FIELD, + statsPropName: "birthtime" + }) + ); service.registerColumns( - new FileIconColumn({sortField: EXT_FIELD}), - new SummaryColumn({sortField: NAME_FIELD, editCellName: summaryEditCellName}), + new FileIconColumn({ sortField: EXT_FIELD }), + new SummaryColumn({ + sortField: NAME_FIELD, + editCellName: summaryEditCellName + }), new StatsDateColumn({ - title: 'Last updated', - description: 'Last updated date', - notePropName: 'mtime', + title: "Last updated", + description: "Last updated date", + notePropName: "mtime", sortField: LAST_UPDATE_FIELD }), new StatsDateColumn({ - title: 'Created', - description: 'Created date', - notePropName: 'birthtime', + title: "Created", + description: "Created date", + notePropName: "birthtime", sortField: BIRTHTIME_FIELD }) - ) + ); - return new Disposables() + return new Disposables(); } -} +}; diff --git a/lib/service-consumers/nv-tags.js b/lib/service-consumers/nv-tags.js index 5bf6ca1..090165a 100644 --- a/lib/service-consumers/nv-tags.js +++ b/lib/service-consumers/nv-tags.js @@ -1,7 +1,7 @@ -var fs = require('fs') -var Disposables = require('../disposables') +var fs = require("fs"); +var Disposables = require("../disposables"); -var XATTR_KEY = 'com.apple.metadata:kMDItemOMUserTags' +var XATTR_KEY = "com.apple.metadata:kMDItemOMUserTags"; // For a *nix compatible platform (targeted MacOSX) this should register custom logic // to support tags compatible with Notational Velocity (see https://notational.net). @@ -20,133 +20,145 @@ var XATTR_KEY = 'com.apple.metadata:kMDItemOMUserTags' // } // } module.exports = { - - getUnsupportedError: function () { + getUnsupportedError: function() { try { // Due to being optionalDependencies they might not exist, if that's the case do nothing and just return - require('bplist') - var xattr = require('fs-xattr') + require("bplist"); + var xattr = require("fs-xattr"); // Filesystem might not support xattrs, verify before continuing - var OS = require('os') - var Path = require('path') - var tmpPath = Path.join(OS.tmpdir(), 'verify-nv-tags' + process.hrtime().toString()) - fs.writeFileSync(tmpPath, '') - xattr.setSync(tmpPath, XATTR_KEY, 'test writing xattrs') - fs.unlinkSync(tmpPath) + var OS = require("os"); + var Path = require("path"); + var tmpPath = Path.join( + OS.tmpdir(), + "verify-nv-tags" + process.hrtime().toString() + ); + fs.writeFileSync(tmpPath, ""); + xattr.setSync(tmpPath, XATTR_KEY, "test writing xattrs"); + fs.unlinkSync(tmpPath); } catch (err) { - return err + return err; } }, - consumeService: function (service) { - if (this.getUnsupportedError()) return new Disposables() + consumeService: function(service) { + if (this.getUnsupportedError()) return new Disposables(); - var bplist = require('bplist') - var xattr = require('fs-xattr') + var bplist = require("bplist"); + var xattr = require("fs-xattr"); - var FILE_PROP_NAME = 'nvtags' - var FIELD_NAME = 'nvtagstr' + var FILE_PROP_NAME = "nvtags"; + var FIELD_NAME = "nvtagstr"; service.registerFileReaders({ notePropName: FILE_PROP_NAME, - read: function (path, stats, callback) { - if (!xattr) return callback(new Error('xattr no longer available, probably due to package being deactivated while in transit')) + read: function(path, stats, callback) { + if (!xattr) + return callback( + new Error( + "xattr no longer available, probably due to package being deactivated while in transit" + ) + ); - xattr.get(path, XATTR_KEY, function (err, plistBuf) { + xattr.get(path, XATTR_KEY, function(err, plistBuf) { if (err) { - if (err.code === 'ENOATTR') { - callback(null, null) // no xattrs set; return empty list + if (err.code === "ENOATTR") { + callback(null, null); // no xattrs set; return empty list } else { - callback(err) // e.g. file path does not exist or such + callback(err); // e.g. file path does not exist or such } } else { - bplist.parseBuffer(plistBuf, function (err, list) { + bplist.parseBuffer(plistBuf, function(err, list) { if (err) { - callback(err) + callback(err); } else { - callback(null, list[0]) + callback(null, list[0]); } - }) + }); } - }) + }); } - }) + }); service.registerFields({ notePropName: FIELD_NAME, - value: function (note) { - var tags = note[FILE_PROP_NAME] + value: function(note) { + var tags = note[FILE_PROP_NAME]; if (Array.isArray(tags)) { - return tags.join(' ') + return tags.join(" "); } } - }) + }); service.registerColumns({ - title: 'NV tags', - description: 'NV Tags', - className: 'nv-tags', + title: "NV tags", + description: "NV Tags", + className: "nv-tags", position: 2, // after Summary sortField: FIELD_NAME, editCellName: FILE_PROP_NAME, width: 20, - editCellStr: function (note) { - var tags = note[FILE_PROP_NAME] + editCellStr: function(note) { + var tags = note[FILE_PROP_NAME]; if (Array.isArray(tags)) { - return tags.join(' ') + return tags.join(" "); } }, - cellContent: function (params) { - var tags = params.note[FILE_PROP_NAME] + cellContent: function(params) { + var tags = params.note[FILE_PROP_NAME]; if (Array.isArray(tags)) { - var searchMatch = params.searchMatch - return tags.map(function (tag) { + var searchMatch = params.searchMatch; + return tags.map(function(tag) { return { - attrs: {className: 'inline-block-tight highlight'}, + attrs: { className: "inline-block-tight highlight" }, content: (searchMatch && searchMatch.content(tag)) || tag - } - }) + }; + }); } } - }) + }); service.registerFileWriters({ editCellName: FILE_PROP_NAME, - write (path, str, callback) { - var tags = str - .trim() - .split(' ') - .reduce((memo, val) => { - if (memo.indexOf(val) === -1) { - memo.push(val) - } - return memo - }, []) + write(path, str, callback) { + var tags = str.trim().split(" ").reduce((memo, val) => { + if (memo.indexOf(val) === -1) { + memo.push(val); + } + return memo; + }, []); - if (tags.length && tags[0] !== '') { - var plistBuf = bplist.create([tags]) - xattr.set(path, XATTR_KEY, plistBuf, callback) + if (tags.length && tags[0] !== "") { + var plistBuf = bplist.create([tags]); + xattr.set(path, XATTR_KEY, plistBuf, callback); } else { - xattr.remove(path, XATTR_KEY, callback) + xattr.remove(path, XATTR_KEY, callback); } - var now = new Date() - fs.utimesSync(path, now, now) // update last access/update times + var now = new Date(); + fs.utimesSync(path, now, now); // update last access/update times } - }) + }); - var editNvTags = function () { - atom.config.set('textual-velocity.editCellName', FILE_PROP_NAME) - } + var editNvTags = function() { + atom.config.set("textual-velocity.editCellName", FILE_PROP_NAME); + }; return new Disposables( - atom.commands.add('.platform-darwin .textual-velocity', 'textual-velocity:edit-nv-tags', editNvTags), - atom.commands.add('.platform-linux .textual-velocity', 'textual-velocity:edit-nv-tags', editNvTags) - ) + atom.commands.add( + ".platform-darwin .textual-velocity", + "textual-velocity:edit-nv-tags", + editNvTags + ), + atom.commands.add( + ".platform-linux .textual-velocity", + "textual-velocity:edit-nv-tags", + editNvTags + ) + ); } -} +}; diff --git a/lib/service-consumers/rename-note.js b/lib/service-consumers/rename-note.js index 3d8007a..eaf4cdc 100644 --- a/lib/service-consumers/rename-note.js +++ b/lib/service-consumers/rename-note.js @@ -1,44 +1,44 @@ /* @flow */ -import fs from 'fs' -import Path from 'path' -import Disposables from '../disposables' +import fs from "fs"; +import Path from "path"; +import Disposables from "../disposables"; export default { - - consumeService (service: Service, editCellName: string) { + consumeService(service: Service, editCellName: string) { service.registerFileWriters({ editCellName, - write (oldPath: string, filename: string, callback: Function) { - filename = filename.split(Path.sep).slice(-1)[0] // i.e. disallow a filename such as 'other-dir/filename.txt' - filename = filename && filename.trim() + write(oldPath: string, filename: string, callback: Function) { + filename = filename.split(Path.sep).slice(-1)[0]; // i.e. disallow a filename such as 'other-dir/filename.txt' + filename = filename && filename.trim(); if (filename) { - const rootPath = oldPath - .split(Path.sep) - .slice(0, -1) - .join(Path.sep) + const rootPath = oldPath.split(Path.sep).slice(0, -1).join(Path.sep); - const newPath = Path.normalize(Path.join(rootPath, filename)) + const newPath = Path.normalize(Path.join(rootPath, filename)); try { - fs.renameSync(oldPath, newPath) // https://nodejs.org/api/fs.html#fs_fs_rename_oldpath_newpath_callback - var now = new Date() - fs.utimesSync(newPath, now, now) // update last access/update times + fs.renameSync(oldPath, newPath); // https://nodejs.org/api/fs.html#fs_fs_rename_oldpath_newpath_callback + var now = new Date(); + fs.utimesSync(newPath, now, now); // update last access/update times } catch (err) { - callback(err) - return + callback(err); + return; } } - callback(null, null) + callback(null, null); } - }) + }); return new Disposables( - atom.commands.add('atom-workspace', 'textual-velocity:rename-note', () => { - atom.config.set('textual-velocity.editCellName', editCellName) - }) - ) + atom.commands.add( + "atom-workspace", + "textual-velocity:rename-note", + () => { + atom.config.set("textual-velocity.editCellName", editCellName); + } + ) + ); } -} +}; diff --git a/lib/service.js b/lib/service.js index 17d8f75..dac7840 100644 --- a/lib/service.js +++ b/lib/service.js @@ -1,48 +1,53 @@ /* @flow */ -const privates = new WeakMap() +const privates = new WeakMap(); export default class Service { - constructor (columns: Columns, fileReaders: FileReaders, fileWriters: FileWriters, noteFields: NoteFields) { - privates.set(this, {columns, fileReaders, fileWriters, noteFields}) + constructor( + columns: Columns, + fileReaders: FileReaders, + fileWriters: FileWriters, + noteFields: NoteFields + ) { + privates.set(this, { columns, fileReaders, fileWriters, noteFields }); } - registerColumns (...items: Array) { - const {columns} = privates.get(this) || {} + registerColumns(...items: Array) { + const { columns } = privates.get(this) || {}; items.forEach(item => { - columns.add(item) - }) + columns.add(item); + }); } - registerFields (...items: Array) { - const {noteFields} = privates.get(this) || {} + registerFields(...items: Array) { + const { noteFields } = privates.get(this) || {}; items.forEach(item => { - noteFields.add(item) - }) + noteFields.add(item); + }); } - registerFileReaders (...items: Array) { - const {fileReaders} = privates.get(this) || {} + registerFileReaders(...items: Array) { + const { fileReaders } = privates.get(this) || {}; items.forEach(item => { - fileReaders.add(item) - }) + fileReaders.add(item); + }); } - deregisterFileReaders (...items: Array) { - const {fileReaders} = privates.get(this) || {} + deregisterFileReaders(...items: Array) { + const { fileReaders } = privates.get(this) || {}; items.forEach(item => { - fileReaders.remove(item) - }) + fileReaders.remove(item); + }); } - registerFileWriters (...items: Array) { - const {fileWriters} = privates.get(this) || {} + registerFileWriters(...items: Array) { + const { fileWriters } = privates.get(this) || {}; items.forEach(item => { - fileWriters.add(item) - }) + fileWriters.add(item); + }); } - dispose () { - privates.delete(this) + dispose() { + privates.delete(this); } } diff --git a/lib/session.js b/lib/session.js index 9727ac2..56f2488 100644 --- a/lib/session.js +++ b/lib/session.js @@ -1,45 +1,61 @@ /* @flow */ -import React from 'react' -import {render} from 'react-dom' -import {Provider} from 'react-redux' -import {createStore, applyMiddleware} from 'redux' -import type {Reducer} from 'redux' // eslint-disable-line -import thunk from 'redux-thunk' -import Disposables from './disposables' -import {startInitialScan, dispose} from './action-creators.js' -import makeEpicMiddleware from './epics/index' -import makeRootReducer from './reducers/index' -import makeApp from './react/app' -import NotesCache from './notes-cache' -import RestartSessionForNewConfigToTakeEffect from './restart-session-for-new-config-to-take-effect' -import ToggleAtomWindow from './toggle-atom-window' -import TogglePanel from './toggle-panel' +import React from "react"; +import { render } from "react-dom"; +import { Provider } from "react-redux"; +import { createStore, applyMiddleware } from "redux"; +import type { Reducer } from "redux"; // eslint-disable-line +import thunk from "redux-thunk"; +import Disposables from "./disposables"; +import { startInitialScan, dispose } from "./action-creators.js"; +import makeEpicMiddleware from "./epics/index"; +import makeRootReducer from "./reducers/index"; +import makeApp from "./react/app"; +import NotesCache from "./notes-cache"; +import RestartSessionForNewConfigToTakeEffect from "./restart-session-for-new-config-to-take-effect"; +import ToggleAtomWindow from "./toggle-atom-window"; +import TogglePanel from "./toggle-panel"; -const privates = new WeakMap() +const privates = new WeakMap(); export default class Session { - async start (dir: string, columns: Columns, fileReaders: FileReaders, fileWriters: FileWriters, noteFields: NoteFields) { + async start( + dir: string, + columns: Columns, + fileReaders: FileReaders, + fileWriters: FileWriters, + noteFields: NoteFields + ) { const panel = atom.workspace.addTopPanel({ - item: document.createElement('div') - }) + item: document.createElement("div") + }); - const rootReducer: Reducer = makeRootReducer(columns, fileReaders, noteFields) - const middlewares = applyMiddleware(thunk, makeEpicMiddleware(columns, fileReaders, fileWriters)) + const rootReducer: Reducer = makeRootReducer( + columns, + fileReaders, + noteFields + ); + const middlewares = applyMiddleware( + thunk, + makeEpicMiddleware(columns, fileReaders, fileWriters) + ); - const notesCache = new NotesCache(dir) + const notesCache = new NotesCache(dir); const initialState: any = { notes: await notesCache.load(), dir - } - const store = createStore(rootReducer, initialState, middlewares) + }; + const store = createStore(rootReducer, initialState, middlewares); - const App = makeApp(columns) - render( - - , panel.getItem()) + const App = makeApp(columns); + render( + + + , + panel.getItem() + ); - store.dispatch(startInitialScan()) + store.dispatch(startInitialScan()); privates.set(this, { notesCache, @@ -50,23 +66,23 @@ export default class Session { new ToggleAtomWindow(panel), new RestartSessionForNewConfigToTakeEffect() ) - }) + }); } - async dispose () { - const {notesCache, panel, store, disposables} = privates.get(this) || {} + async dispose() { + const { notesCache, panel, store, disposables } = privates.get(this) || {}; try { - await notesCache.save(store.getState().notes) + await notesCache.save(store.getState().notes); } catch (err) { - console.warn('textual-velocity: could not cache notes', err) + console.warn("textual-velocity: could not cache notes", err); } - notesCache.dispose() + notesCache.dispose(); - store.dispatch(dispose()) - panel.destroy() - disposables.dispose() + store.dispatch(dispose()); + panel.destroy(); + disposables.dispose(); - privates.delete(this) + privates.delete(this); } } diff --git a/lib/toggle-atom-window.js b/lib/toggle-atom-window.js index 583974b..fda6d95 100644 --- a/lib/toggle-atom-window.js +++ b/lib/toggle-atom-window.js @@ -1,29 +1,36 @@ /* @flow */ -const privates = new WeakMap() +const privates = new WeakMap(); export default class ToggleAtomWindow { - constructor (panel: Atom$Panel) { - privates.set(this, atom.commands.add('atom-workspace', 'textual-velocity:toggle-atom-window', () => { - if (atom.getCurrentWindow().isFocused()) { - if (panel.isVisible()) { - atom.hide() // hides Atom window - } else { - panel.show() + constructor(panel: Atom$Panel) { + privates.set( + this, + atom.commands.add( + "atom-workspace", + "textual-velocity:toggle-atom-window", + () => { + if (atom.getCurrentWindow().isFocused()) { + if (panel.isVisible()) { + atom.hide(); // hides Atom window + } else { + panel.show(); + } + } else { + atom.show(); + atom.focus(); + panel.show(); + } } - } else { - atom.show() - atom.focus() - panel.show() - } - })) + ) + ); } - dispose () { - const disposable = privates.get(this) || {} + dispose() { + const disposable = privates.get(this) || {}; - disposable.dispose() + disposable.dispose(); - privates.delete(this) + privates.delete(this); } } diff --git a/lib/toggle-panel.js b/lib/toggle-panel.js index 262652c..a3a0f64 100644 --- a/lib/toggle-panel.js +++ b/lib/toggle-panel.js @@ -1,23 +1,30 @@ /* @flow */ -const privates = new WeakMap() +const privates = new WeakMap(); export default class TogglePanel { - constructor (panel: Atom$Panel) { - privates.set(this, atom.commands.add('atom-workspace', 'textual-velocity:toggle-panel', () => { - if (panel.isVisible()) { - panel.hide() - } else { - panel.show() - } - })) + constructor(panel: Atom$Panel) { + privates.set( + this, + atom.commands.add( + "atom-workspace", + "textual-velocity:toggle-panel", + () => { + if (panel.isVisible()) { + panel.hide(); + } else { + panel.show(); + } + } + ) + ); } - dispose () { - const disposable = privates.get(this) || {} + dispose() { + const disposable = privates.get(this) || {}; - disposable.dispose() + disposable.dispose(); - privates.delete(this) + privates.delete(this); } } diff --git a/package.json b/package.json index 6ccdc23..cd52571 100644 --- a/package.json +++ b/package.json @@ -59,21 +59,31 @@ "babel-plugin-syntax-flow": "6.18.0", "babel-plugin-transform-flow-strip-types": "6.22.0", "eslint": "3.19.0", - "eslint-config-standard": "10.2.1", - "eslint-config-standard-jsx": "4.0.1", + "eslint-config-prettier": "2.2.0", "eslint-plugin-flowtype": "2.33.0", - "flow-bin": "0.46.0", "eslint-plugin-import": "2.2.0", "eslint-plugin-node": "4.2.2", + "eslint-plugin-prettier": "2.1.2", "eslint-plugin-promise": "3.5.0", "eslint-plugin-react": "7.0.1", - "eslint-plugin-standard": "3.0.1", + "flow-bin": "0.46.0", + "husky": "0.14.0", + "lint-staged": "4.0.0", + "prettier": "1.4.4", "redux-mock-store": "1.2.3", "temp": "0.8.3" }, "scripts": { + "precommit": "lint-staged", "flow": "flow; test $? -eq 0 -o $? -eq 2", - "lint": "eslint 'lib/**/*.js' && eslint 'spec/**/*.js'", + "lint": "eslint \"{lib,spec}/**/*.js\"", + "lint-prettier": "prettier --parser flow --list-different \"{lib,spec}/**/*.js\"", "test": "apm test && npm run flow && npm run lint" + }, + "lint-staged": { + "{lib,spec}/**/*.js": [ + "prettier --parser flow --write", + "git add" + ] } } diff --git a/spec/_async-spec-helpers.js b/spec/_async-spec-helpers.js index e4ba183..6f6cd11 100644 --- a/spec/_async-spec-helpers.js +++ b/spec/_async-spec-helpers.js @@ -1,63 +1,63 @@ /** @babel */ // from https://github.com/atom/atom/blob/efae2e08c3f902149431732cbd550aea09748acc/spec/async-spec-helpers.js -export function beforeEach (fn) { - global.beforeEach(function () { - const result = fn() +export function beforeEach(fn) { + global.beforeEach(function() { + const result = fn(); if (result instanceof Promise) { - waitsForPromise(() => result) + waitsForPromise(() => result); } - }) + }); } -export function afterEach (fn) { - global.afterEach(function () { - const result = fn() +export function afterEach(fn) { + global.afterEach(function() { + const result = fn(); if (result instanceof Promise) { - waitsForPromise(() => result) + waitsForPromise(() => result); } - }) + }); } -['it', 'fit', 'ffit', 'fffit'].forEach(function (name) { - module.exports[name] = function (description, fn) { - global[name](description, function () { - const result = fn() +["it", "fit", "ffit", "fffit"].forEach(function(name) { + module.exports[name] = function(description, fn) { + global[name](description, function() { + const result = fn(); if (result instanceof Promise) { - waitsForPromise(() => result) + waitsForPromise(() => result); } - }) - } -}) + }); + }; +}); -export async function conditionPromise (condition) { - const startTime = Date.now() +export async function conditionPromise(condition) { + const startTime = Date.now(); while (true) { - await timeoutPromise(100) + await timeoutPromise(100); if (await condition()) { - return + return; } if (Date.now() - startTime > 5000) { - throw new Error("Timed out waiting on condition") + throw new Error("Timed out waiting on condition"); } } } -export function timeoutPromise (timeout) { - return new Promise(function (resolve) { - global.setTimeout(resolve, timeout) - }) +export function timeoutPromise(timeout) { + return new Promise(function(resolve) { + global.setTimeout(resolve, timeout); + }); } -function waitsForPromise (fn) { - const promise = fn() - global.waitsFor('spec promise to resolve', function (done) { - promise.then(done, function (error) { - jasmine.getEnv().currentSpec.fail(error) - done() - }) - }) +function waitsForPromise(fn) { + const promise = fn(); + global.waitsFor("spec promise to resolve", function(done) { + promise.then(done, function(error) { + jasmine.getEnv().currentSpec.fail(error); + done(); + }); + }); } diff --git a/spec/_fix-spec.js b/spec/_fix-spec.js index 1f95894..0a9d37a 100644 --- a/spec/_fix-spec.js +++ b/spec/_fix-spec.js @@ -1,12 +1,15 @@ /* @flow */ -import defaultConfig from '../lib/default-config' +import defaultConfig from "../lib/default-config"; // Remove the custom equalityTest added by atom: // https://github.com/atom/atom/blob/1500381ac9216bd533199f5b59460b5ac596527c/spec/spec-helper.coffee#L45 // so assertions such as `expect(foo).toEqual(jasmine.any(Object))` works -global.jasmine.getEnv().equalityTesters_ = [] +global.jasmine.getEnv().equalityTesters_ = []; beforeEach(() => { - atom.config.setSchema('textual-velocity', {type: 'object', properties: JSON.parse(JSON.stringify(defaultConfig))}) -}) + atom.config.setSchema("textual-velocity", { + type: "object", + properties: JSON.parse(JSON.stringify(defaultConfig)) + }); +}); diff --git a/spec/atom-rxjs-observables-spec.js b/spec/atom-rxjs-observables-spec.js index 55b9aaa..e37824e 100644 --- a/spec/atom-rxjs-observables-spec.js +++ b/spec/atom-rxjs-observables-spec.js @@ -1,67 +1,67 @@ /* @flow */ -import {observeConfig} from '../lib/atom-rxjs-observables' +import { observeConfig } from "../lib/atom-rxjs-observables"; -describe('Atom RxJS observables', () => { - describe('observeConfig', function () { - describe('given valid input', function () { - let observation$ - let nextSpy, errorSpy - let subscription: rxjs$ISubscription +describe("Atom RxJS observables", () => { + describe("observeConfig", function() { + describe("given valid input", function() { + let observation$; + let nextSpy, errorSpy; + let subscription: rxjs$ISubscription; - beforeEach(function () { - atom.config.set('textual-velocity.path', '/notes') + beforeEach(function() { + atom.config.set("textual-velocity.path", "/notes"); - nextSpy = jasmine.createSpy('next') - errorSpy = jasmine.createSpy('error') + nextSpy = jasmine.createSpy("next"); + errorSpy = jasmine.createSpy("error"); - observation$ = observeConfig('textual-velocity.path') - subscription = observation$.subscribe(nextSpy, errorSpy) - }) + observation$ = observeConfig("textual-velocity.path"); + subscription = observation$.subscribe(nextSpy, errorSpy); + }); - afterEach(function () { - subscription.unsubscribe() - }) + afterEach(function() { + subscription.unsubscribe(); + }); - it('should be called when subscribed', function () { - expect(nextSpy).toHaveBeenCalledWith('/notes') - expect(nextSpy.calls.length).toEqual(1) - }) + it("should be called when subscribed", function() { + expect(nextSpy).toHaveBeenCalledWith("/notes"); + expect(nextSpy.calls.length).toEqual(1); + }); - it('should call observer when observed object changed', function () { - nextSpy.reset() - atom.config.set('textual-velocity.path', '/other/dir') - expect(nextSpy).toHaveBeenCalledWith('/other/dir') - expect(nextSpy.calls.length).toEqual(1) - }) + it("should call observer when observed object changed", function() { + nextSpy.reset(); + atom.config.set("textual-velocity.path", "/other/dir"); + expect(nextSpy).toHaveBeenCalledWith("/other/dir"); + expect(nextSpy.calls.length).toEqual(1); + }); - it('should be an observable', function () { - expect(observation$.switchMap).toEqual(jasmine.any(Function)) - }) + it("should be an observable", function() { + expect(observation$.switchMap).toEqual(jasmine.any(Function)); + }); - describe('when unsubscribed', function () { - beforeEach(function () { - subscription.unsubscribe() - nextSpy.reset() - }) + describe("when unsubscribed", function() { + beforeEach(function() { + subscription.unsubscribe(); + nextSpy.reset(); + }); - it('should no longer observe changes when unsubscribed', function () { - atom.config.set('textual-velocity.path', '/other/dir') - expect(nextSpy).not.toHaveBeenCalled() - }) - }) - }) + it("should no longer observe changes when unsubscribed", function() { + atom.config.set("textual-velocity.path", "/other/dir"); + expect(nextSpy).not.toHaveBeenCalled(); + }); + }); + }); - describe('given invalid key', function () { - it('should throw error', function () { - expect(() => observeConfig('')).toThrow() - expect(() => observeConfig(' ')).toThrow() + describe("given invalid key", function() { + it("should throw error", function() { + expect(() => observeConfig("")).toThrow(); + expect(() => observeConfig(" ")).toThrow(); - let input: any - expect(() => observeConfig(input)).toThrow() - input = null - expect(() => observeConfig(input)).toThrow() - }) - }) - }) -}) + let input: any; + expect(() => observeConfig(input)).toThrow(); + input = null; + expect(() => observeConfig(input)).toThrow(); + }); + }); + }); +}); diff --git a/spec/columns-spec.js b/spec/columns-spec.js index 62d3750..51fdd06 100644 --- a/spec/columns-spec.js +++ b/spec/columns-spec.js @@ -1,59 +1,63 @@ /* @flow */ -import Columns from '../lib/columns' +import Columns from "../lib/columns"; -describe('columns', () => { - let columns, schema +describe("columns", () => { + let columns, schema; - beforeEach(function () { - atom.config.setSchema('textual-velocity.sortField', { - type: 'string', - default: 'name' - }) - atom.config.set('textual-velocity.sortField', 'name') + beforeEach(function() { + atom.config.setSchema("textual-velocity.sortField", { + type: "string", + default: "name" + }); + atom.config.set("textual-velocity.sortField", "name"); - columns = new Columns() + columns = new Columns(); columns.add({ - cellContent: () => 'some content', - description: 'A test column', - sortField: 'test-field', - title: 'test', + cellContent: () => "some content", + description: "A test column", + sortField: "test-field", + title: "test", width: 25 - }) + }); columns.add({ - cellContent: () => 'name', - description: 'Filename w/o extension', - sortField: 'name', - title: 'Name', + cellContent: () => "name", + description: "Filename w/o extension", + sortField: "name", + title: "Name", width: 50 - }) + }); columns.add({ - cellContent: () => 'ext', - description: 'extension of filename', - sortField: 'ext', - title: 'File extension', + cellContent: () => "ext", + description: "extension of filename", + sortField: "ext", + title: "File extension", width: 25 - }) - schema = atom.config.getSchema('textual-velocity.sortField') - }) + }); + schema = atom.config.getSchema("textual-velocity.sortField"); + }); - it('should change the schema', function () { - expect(schema.type).toEqual('string') - expect(schema.default).toEqual('test-field') + it("should change the schema", function() { + expect(schema.type).toEqual("string"); + expect(schema.default).toEqual("test-field"); expect(schema.enum).toEqual([ - {value: 'test-field', description: 'test'}, - {value: 'name', description: 'Name'}, - {value: 'ext', description: 'File extension'} - ]) - }) + { value: "test-field", description: "test" }, + { value: "name", description: "Name" }, + { value: "ext", description: "File extension" } + ]); + }); - it('should maintain config value even after columns are added/schema changed', function () { - expect(atom.config.get('textual-velocity.sortField')).toEqual('name') - }) + it("should maintain config value even after columns are added/schema changed", function() { + expect(atom.config.get("textual-velocity.sortField")).toEqual("name"); + }); - describe('.map', function () { - it('should map results', function () { - expect(columns.map(column => column.sortField)).toEqual(['test-field', 'name', 'ext']) - }) - }) -}) + describe(".map", function() { + it("should map results", function() { + expect(columns.map(column => column.sortField)).toEqual([ + "test-field", + "name", + "ext" + ]); + }); + }); +}); diff --git a/spec/columns/file-icon-column-spec.js b/spec/columns/file-icon-column-spec.js index 5c86f39..4617acf 100644 --- a/spec/columns/file-icon-column-spec.js +++ b/spec/columns/file-icon-column-spec.js @@ -1,55 +1,55 @@ -'use babel' +"use babel"; -import FileIconColumn from '../../lib/columns/file-icon-column' +import FileIconColumn from "../../lib/columns/file-icon-column"; -describe('columns/file-icon-column', function () { - let column +describe("columns/file-icon-column", function() { + let column; - beforeEach(function () { - column = new FileIconColumn({sortField: 'ext'}) - }) + beforeEach(function() { + column = new FileIconColumn({ sortField: "ext" }); + }); - describe('.sortField', function () { - it('should return given sort field value', function () { - expect(column.sortField).toEqual('ext') - }) - }) + describe(".sortField", function() { + it("should return given sort field value", function() { + expect(column.sortField).toEqual("ext"); + }); + }); - describe('.cellContent', function () { - let note, path, cellContent + describe(".cellContent", function() { + let note, path, cellContent; - beforeEach(function () { - path = '/notes/markdown.md' + beforeEach(function() { + path = "/notes/markdown.md"; note = { - id: '', - name: 'markdown', + id: "", + name: "markdown", fileIcons: null, - ext: '.md' - } - cellContent = column.cellContent({note: note, path: path}) - }) + ext: ".md" + }; + cellContent = column.cellContent({ note: note, path: path }); + }); - it('should return a kind of AST from which a DOM can be created', function () { - expect(cellContent).toEqual(jasmine.any(Object), 'title') + it("should return a kind of AST from which a DOM can be created", function() { + expect(cellContent).toEqual(jasmine.any(Object), "title"); expect(cellContent.attrs).toEqual({ - className: 'icon icon-file-text', - 'data-name': 'markdown.md' - }) - }) - - describe('when note.fileIcons are available', function () { - beforeEach(function () { - note.fileIcons = 'icon-file-text medium-blue' - cellContent = column.cellContent({note: note, path: path}) - }) - - it('should utilize file icons instead', function () { - expect(cellContent).toEqual(jasmine.any(Object), 'title') + className: "icon icon-file-text", + "data-name": "markdown.md" + }); + }); + + describe("when note.fileIcons are available", function() { + beforeEach(function() { + note.fileIcons = "icon-file-text medium-blue"; + cellContent = column.cellContent({ note: note, path: path }); + }); + + it("should utilize file icons instead", function() { + expect(cellContent).toEqual(jasmine.any(Object), "title"); expect(cellContent.attrs).toEqual({ - className: 'icon-file-text medium-blue', - 'data-name': 'markdown.md' - }) - }) - }) - }) -}) + className: "icon-file-text medium-blue", + "data-name": "markdown.md" + }); + }); + }); + }); +}); diff --git a/spec/columns/stats-date-column-spec.js b/spec/columns/stats-date-column-spec.js index 6cdc240..52feed7 100644 --- a/spec/columns/stats-date-column-spec.js +++ b/spec/columns/stats-date-column-spec.js @@ -1,50 +1,52 @@ -'use babel' +"use babel"; -import StatsDateColumn from '../../lib/columns/stats-date-column' +import StatsDateColumn from "../../lib/columns/stats-date-column"; -describe('columns/stats-date-column', function () { - let column +describe("columns/stats-date-column", function() { + let column; - beforeEach(function () { + beforeEach(function() { column = new StatsDateColumn({ - sortField: 'created-date', - title: 'Created date', - notePropName: 'birthtime' - }) - }) - - describe('.sortField', function () { - it('should return sort field', function () { - expect(column.sortField).toEqual('created-date') - }) - }) - - describe('.title', function () { - it('should return title', function () { - expect(column.title).toEqual('Created date') - }) - }) - - describe('.cellContent', function () { - let path, note - - beforeEach(function () { - path = '/notes/markdown.md' + sortField: "created-date", + title: "Created date", + notePropName: "birthtime" + }); + }); + + describe(".sortField", function() { + it("should return sort field", function() { + expect(column.sortField).toEqual("created-date"); + }); + }); + + describe(".title", function() { + it("should return title", function() { + expect(column.title).toEqual("Created date"); + }); + }); + + describe(".cellContent", function() { + let path, note; + + beforeEach(function() { + path = "/notes/markdown.md"; note = { - id: '', - name: 'markdown', - ext: '.md' - } - }) - - it('should return an empty string if there is no date for given prop', function () { - expect(column.cellContent({note: note, path: path})).toEqual('') - }) - - it('should return diffing time from now', function () { - note.stats = {birthtime: new Date()} - expect(column.cellContent({note: note, path: path})).toEqual(jasmine.any(String)) - expect(column.cellContent({note: note, path: path})).not.toEqual('') - }) - }) -}) + id: "", + name: "markdown", + ext: ".md" + }; + }); + + it("should return an empty string if there is no date for given prop", function() { + expect(column.cellContent({ note: note, path: path })).toEqual(""); + }); + + it("should return diffing time from now", function() { + note.stats = { birthtime: new Date() }; + expect(column.cellContent({ note: note, path: path })).toEqual( + jasmine.any(String) + ); + expect(column.cellContent({ note: note, path: path })).not.toEqual(""); + }); + }); +}); diff --git a/spec/columns/summary-column-spec.js b/spec/columns/summary-column-spec.js index 1066207..432c4b1 100644 --- a/spec/columns/summary-column-spec.js +++ b/spec/columns/summary-column-spec.js @@ -1,103 +1,145 @@ -'use babel' +"use babel"; -import SummaryColumn from '../../lib/columns/summary-column' -import SearchMatch from '../../lib/search-match' +import SummaryColumn from "../../lib/columns/summary-column"; +import SearchMatch from "../../lib/search-match"; -describe('columns/summary-column', function () { - let column +describe("columns/summary-column", function() { + let column; - beforeEach(function () { - column = new SummaryColumn({sortField: 'name'}) - }) + beforeEach(function() { + column = new SummaryColumn({ sortField: "name" }); + }); - describe('.sortField', function () { - it('should return given sort field value', function () { - expect(column.sortField).toEqual('name') - }) - }) + describe(".sortField", function() { + it("should return given sort field value", function() { + expect(column.sortField).toEqual("name"); + }); + }); - describe('.cellContent', function () { - let note, path + describe(".cellContent", function() { + let note, path; - beforeEach(function () { - path = '/notes/markdown.md' + beforeEach(function() { + path = "/notes/markdown.md"; note = { - id: '', - name: 'markdown', - ext: '.md', - content: '# testing markdown\nshould be **Zzz** by now tho' - } - }) + id: "", + name: "markdown", + ext: ".md", + content: "# testing markdown\nshould be **Zzz** by now tho" + }; + }); - describe('when there is no query applied', function () { - let cellContent + describe("when there is no query applied", function() { + let cellContent; - beforeEach(function () { - cellContent = column.cellContent({note: note, path: path}) - }) + beforeEach(function() { + cellContent = column.cellContent({ note: note, path: path }); + }); - it('should return a kind of AST from which a DOM can be created', function () { - expect(cellContent).toEqual(jasmine.any(Array)) - expect(cellContent[0]).toEqual('markdown', 'title') - expect(cellContent[1]).toEqual({attrs: {className: 'text-subtle'}, content: '.md'}) - expect(cellContent[2]).toEqual(jasmine.any(String), 'should be a separator between title and note content preview') - expect(cellContent[3]).toEqual(jasmine.any(Object), 'note content preview') - expect(cellContent[3].attrs).toEqual(jasmine.any(Object)) - expect(cellContent[3].content).toEqual(jasmine.any(String)) - }) - }) + it("should return a kind of AST from which a DOM can be created", function() { + expect(cellContent).toEqual(jasmine.any(Array)); + expect(cellContent[0]).toEqual("markdown", "title"); + expect(cellContent[1]).toEqual({ + attrs: { className: "text-subtle" }, + content: ".md" + }); + expect(cellContent[2]).toEqual( + jasmine.any(String), + "should be a separator between title and note content preview" + ); + expect(cellContent[3]).toEqual( + jasmine.any(Object), + "note content preview" + ); + expect(cellContent[3].attrs).toEqual(jasmine.any(Object)); + expect(cellContent[3].content).toEqual(jasmine.any(String)); + }); + }); - describe('when there is a search query', function () { - let cellContent + describe("when there is a search query", function() { + let cellContent; - it('should provide what parts of title and note content preview that should be highlighted', function () { + it("should provide what parts of title and note content preview that should be highlighted", function() { // Match in the middle cellContent = column.cellContent({ note: note, path: path, - searchMatch: new SearchMatch(/[kƘƙꝀꝁḰḱǨǩḲḳḴḵκϰ₭][dĎďḊḋḐḑḌḍḒḓḎḏĐđD̦d̦ƉɖƊɗƋƌᵭᶁᶑȡᴅDdð]/i) - }) // as generated by sifter.js - expect(cellContent).toEqual(jasmine.any(Array)) - expect(cellContent[0]).toEqual(jasmine.any(Array), 'title w/ highlight') + searchMatch: new SearchMatch( + /[kƘƙꝀꝁḰḱǨǩḲḳḴḵκϰ₭][dĎďḊḋḐḑḌḍḒḓḎḏĐđD̦d̦ƉɖƊɗƋƌᵭᶁᶑȡᴅDdð]/i + ) + }); // as generated by sifter.js + expect(cellContent).toEqual(jasmine.any(Array)); + expect(cellContent[0]).toEqual( + jasmine.any(Array), + "title w/ highlight" + ); expect(cellContent[0]).toEqual([ - 'mar', {attrs: {className: 'text-highlight'}, content: 'kd'}, 'own' - ]) - expect(cellContent[1]).toEqual({attrs: {className: 'text-subtle'}, content: '.md'}) - expect(cellContent[2]).toEqual(' - ') - expect(cellContent[3].content).toEqual( - ['# testing mar', {attrs: {className: 'text-highlight'}, content: 'kd'}, 'own\nshould be **Zzz** by now tho'] - ) + "mar", + { attrs: { className: "text-highlight" }, content: "kd" }, + "own" + ]); + expect(cellContent[1]).toEqual({ + attrs: { className: "text-subtle" }, + content: ".md" + }); + expect(cellContent[2]).toEqual(" - "); + expect(cellContent[3].content).toEqual([ + "# testing mar", + { attrs: { className: "text-highlight" }, content: "kd" }, + "own\nshould be **Zzz** by now tho" + ]); // Match at beginning of string cellContent = column.cellContent({ note: note, path: path, searchMatch: new SearchMatch(/m[aḀḁĂăÂâǍǎȺⱥȦȧẠạÄäÀàÁáĀāÃãÅåąĄÃąĄ]/i) - }) - expect(cellContent[0]).toEqual(['', {attrs: {className: 'text-highlight'}, content: 'ma'}, 'rkdown']) - expect(cellContent[1]).toEqual({attrs: {className: 'text-subtle'}, content: '.md'}) + }); + expect(cellContent[0]).toEqual([ + "", + { attrs: { className: "text-highlight" }, content: "ma" }, + "rkdown" + ]); + expect(cellContent[1]).toEqual({ + attrs: { className: "text-subtle" }, + content: ".md" + }); // Match at end of string cellContent = column.cellContent({ note: note, path: path, - searchMatch: new SearchMatch(/[wẂẃẀẁŴŵẄẅẆẇẈẉ][nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲȠƞᵰᶇɳȵɴNnŊŋ]/i) - }) - expect(cellContent[0]).toEqual(['markdo', {attrs: {className: 'text-highlight'}, content: 'wn'}, '']) - expect(cellContent[1]).toEqual({attrs: {className: 'text-subtle'}, content: '.md'}) + searchMatch: new SearchMatch( + /[wẂẃẀẁŴŵẄẅẆẇẈẉ][nŃńǸǹŇňÑñṄṅŅņṆṇṊṋṈṉN̈n̈ƝɲȠƞᵰᶇɳȵɴNnŊŋ]/i + ) + }); + expect(cellContent[0]).toEqual([ + "markdo", + { attrs: { className: "text-highlight" }, content: "wn" }, + "" + ]); + expect(cellContent[1]).toEqual({ + attrs: { className: "text-subtle" }, + content: ".md" + }); // No match cellContent = column.cellContent({ note: note, path: path, searchMatch: new SearchMatch(/Zzz/i) - }) - expect(cellContent[0]).toEqual('markdown') - expect(cellContent[1]).toEqual({attrs: {className: 'text-subtle'}, content: '.md'}, 'match of end should only contain rest+end') - expect(cellContent[3].content).toEqual( - ['…arkdown\nshould be **', {attrs: {className: 'text-highlight'}, content: 'Zzz'}, '** by now tho'] - ) - }) - }) - }) -}) + }); + expect(cellContent[0]).toEqual("markdown"); + expect(cellContent[1]).toEqual( + { attrs: { className: "text-subtle" }, content: ".md" }, + "match of end should only contain rest+end" + ); + expect(cellContent[3].content).toEqual([ + "…arkdown\nshould be **", + { attrs: { className: "text-highlight" }, content: "Zzz" }, + "** by now tho" + ]); + }); + }); + }); +}); diff --git a/spec/dispatch-keydown-event.js b/spec/dispatch-keydown-event.js index de603c7..0f19aaa 100644 --- a/spec/dispatch-keydown-event.js +++ b/spec/dispatch-keydown-event.js @@ -1,15 +1,15 @@ /* @flow */ // Unfortunately TestUtils.Simulate.keyDown(input, {key: 'Enter', keyCode: 13}) can't be used inside atom -export default function dispatchKeyDownEvent (el: any, opts: Object = {}) { - if (!el) throw new Error('el is missing!') +export default function dispatchKeyDownEvent(el: any, opts: Object = {}) { + if (!el) throw new Error("el is missing!"); // Chrome doesn't play well with keydown events // from https://code.google.com/p/chromium/issues/detail?id=327853&q=KeyboardEvent&colspec=ID%20Pri%20M%20Stars%20ReleaseBlock%20Cr%20Status%20Owner%20Summary%20OS%20Modified - var e: any = document.createEvent('Events') + var e: any = document.createEvent("Events"); // KeyboardEvents bubble and are cancelable. // https://developer.mozilla.org/en-US/docs/Web/API/event.initEvent - e.initEvent('keydown', true, true) - e.keyCode = opts.keyCode - el.dispatchEvent(e) + e.initEvent("keydown", true, true); + e.keyCode = opts.keyCode; + el.dispatchEvent(e); } diff --git a/spec/epics/active-pane-item-spec.js b/spec/epics/active-pane-item-spec.js index 5f4d3d7..4189c2f 100644 --- a/spec/epics/active-pane-item-spec.js +++ b/spec/epics/active-pane-item-spec.js @@ -1,18 +1,18 @@ /* @flow */ -import {createEpicMiddleware} from 'redux-observable' -import configureMockStore from 'redux-mock-store' -import activePaneItemEpic from '../../lib/epics/active-pane-item' -import * as A from '../../lib/action-creators' +import { createEpicMiddleware } from "redux-observable"; +import configureMockStore from "redux-mock-store"; +import activePaneItemEpic from "../../lib/epics/active-pane-item"; +import * as A from "../../lib/action-creators"; -describe('epics/active-pane-item', () => { - let state: State - let store +describe("epics/active-pane-item", () => { + let state: State; + let store; beforeEach(() => { state = { columnHeaders: [], - dir: '/notes', + dir: "/notes", editCellName: null, initialScan: { done: false, @@ -20,94 +20,96 @@ describe('epics/active-pane-item', () => { }, listHeight: 75, notes: { - 'alice.txt': { - id: '0', - name: 'alice', - ext: 'txt', - path: '/notes/alice.txt', - stats: {mtime: new Date()} + "alice.txt": { + id: "0", + name: "alice", + ext: "txt", + path: "/notes/alice.txt", + stats: { mtime: new Date() } }, - 'bob.md': { - id: '1', - name: 'bob', - ext: 'md', - path: '/notes/bob.md', - stats: {mtime: new Date()} + "bob.md": { + id: "1", + name: "bob", + ext: "md", + path: "/notes/bob.md", + stats: { mtime: new Date() } }, - 'cesar.txt': { - id: '2', - name: 'cesar', - ext: 'txt', - path: '/notes/cesar.txt', - stats: {mtime: new Date()} + "cesar.txt": { + id: "2", + name: "cesar", + ext: "txt", + path: "/notes/cesar.txt", + stats: { mtime: new Date() } } }, - queryOriginal: '', + queryOriginal: "", rowHeight: 25, scrollTop: 0, selectedNote: null, sifterResult: { items: [ - {id: 'alice.txt', score: 1.0}, - {id: 'bob.md', score: 0.9}, - {id: 'cesar.txt', score: 0.8} + { id: "alice.txt", score: 1.0 }, + { id: "bob.md", score: 0.9 }, + { id: "cesar.txt", score: 0.8 } ], options: { - fields: ['name', 'ext'], + fields: ["name", "ext"], sort: [ - {field: 'name', direction: 'asc'}, - {field: '$score', direction: 'desc'} + { field: "name", direction: "asc" }, + { field: "$score", direction: "desc" } ] }, - query: '', + query: "", tokens: [], total: 3 } - } + }; - jasmine.useRealClock() - jasmine.Clock.useMock() - spyOn(Date, 'now').andReturn(0) + jasmine.useRealClock(); + jasmine.Clock.useMock(); + spyOn(Date, "now").andReturn(0); - const epicMiddleware = createEpicMiddleware(activePaneItemEpic) - const mockStore = configureMockStore([epicMiddleware]) - store = mockStore(state) - store.dispatch(A.resizeList(123)) // dummy action, just to have action$ to have a value to start with - }) + const epicMiddleware = createEpicMiddleware(activePaneItemEpic); + const mockStore = configureMockStore([epicMiddleware]); + store = mockStore(state); + store.dispatch(A.resizeList(123)); // dummy action, just to have action$ to have a value to start with + }); - describe('when active pane item is changed due to non-package interaction (e.g. open recent file or such)', function () { - beforeEach(function () { - store.clearActions() + describe("when active pane item is changed due to non-package interaction (e.g. open recent file or such)", function() { + beforeEach(function() { + store.clearActions(); - Date.now.andReturn(500) // for open-file event to not be interpreted as a package interaction + Date.now.andReturn(500); // for open-file event to not be interpreted as a package interaction - let done = false - atom.workspace.open('/notes/bob.md').then(() => { - jasmine.Clock.tick(1000) - done = true - }) - waitsFor(() => done) - }) + let done = false; + atom.workspace.open("/notes/bob.md").then(() => { + jasmine.Clock.tick(1000); + done = true; + }); + waitsFor(() => done); + }); - it('should dispatch action to select matching note', function () { - expect(store.getActions().slice(-1)[0]).toEqual(A.changedActivePaneItem('/notes/bob.md')) - }) - }) + it("should dispatch action to select matching note", function() { + expect(store.getActions().slice(-1)[0]).toEqual( + A.changedActivePaneItem("/notes/bob.md") + ); + }); + }); - describe('when active pane item is changed due to interaction with plugin, e.g. select note (=> preview)', function () { - beforeEach(function () { - store.clearActions() + describe("when active pane item is changed due to interaction with plugin, e.g. select note (=> preview)", function() { + beforeEach(function() { + store.clearActions(); - let done = false - atom.workspace.open('/notes/untitled.md').then(() => { - jasmine.Clock.tick(1000) - done = true - }) - waitsFor(() => done) - }) + let done = false; + atom.workspace.open("/notes/untitled.md").then(() => { + jasmine.Clock.tick(1000); + done = true; + }); + waitsFor(() => done); + }); - it('should not dispatch any action', function () { - expect(store.getActions()).toEqual([]) - }) - }) -}) + it("should not dispatch any action", function() { + expect(store.getActions()).toEqual([]); + }); + }); +}); diff --git a/spec/epics/config-changes-spec.js b/spec/epics/config-changes-spec.js index b52a183..4344671 100644 --- a/spec/epics/config-changes-spec.js +++ b/spec/epics/config-changes-spec.js @@ -1,114 +1,117 @@ /* @flow */ -import {createEpicMiddleware} from 'redux-observable' -import configureMockStore from 'redux-mock-store' -import configChangesEpic from '../../lib/epics/config-changes' -import * as A from '../../lib/action-creators' +import { createEpicMiddleware } from "redux-observable"; +import configureMockStore from "redux-mock-store"; +import configChangesEpic from "../../lib/epics/config-changes"; +import * as A from "../../lib/action-creators"; -const epicMiddleware = createEpicMiddleware(configChangesEpic) -const mockStore = configureMockStore([epicMiddleware]) +const epicMiddleware = createEpicMiddleware(configChangesEpic); +const mockStore = configureMockStore([epicMiddleware]); -describe('epics/config-changes', () => { - let store +describe("epics/config-changes", () => { + let store; beforeEach(() => { - store = mockStore() - }) - - afterEach(function () { - epicMiddleware.replaceEpic(configChangesEpic) - }) - - it('should yield actions for initial values of config', function () { - const dispatchedActions = store.getActions() - expect(dispatchedActions[0]).toEqual(A.changeListHeight(150)) - expect(dispatchedActions[1]).toEqual(A.changeRowHeight(20)) - expect(dispatchedActions[2]).toEqual(A.changeSortDirection('desc')) - expect(dispatchedActions[3]).toEqual(A.changeSortField('name')) - }) - - describe('when resized list action', function () { - let listHeightSpy - - beforeEach(function () { - listHeightSpy = jasmine.createSpy('listHeight') - atom.config.onDidChange('textual-velocity.listHeight', listHeightSpy) - store.dispatch(A.resizeList(123)) - - waitsFor(() => listHeightSpy.calls.length > 0) - }) - - it('should have updated list Height', function () { - expect(atom.config.get('textual-velocity.listHeight')).toEqual(123) - }) - - it('should have yielded a last action', function () { - const lastActions = store.getActions().slice(-2) - expect(lastActions[0]).toEqual(A.resizeList(123)) - expect(lastActions[1]).toEqual(A.changeListHeight(123)) - }) - }) - - describe('when changed row height', function () { - let rowHeightSpy - - beforeEach(function () { - rowHeightSpy = jasmine.createSpy('rowHeight') - atom.config.onDidChange('textual-velocity.rowHeight', rowHeightSpy) - store.dispatch(A.changeRowHeight(26)) - - waitsFor(() => rowHeightSpy.calls.length > 0) - }) - - it('should have updated list Height', function () { - expect(atom.config.get('textual-velocity.rowHeight')).toEqual(26) - }) - - it('should have yielded a last action', function () { - const lastActions = store.getActions().slice(-1) - expect(lastActions[0]).toEqual(A.changeRowHeight(26)) - }) - }) - - describe('when changed sort direction', function () { - let sortDirectionSpy - - beforeEach(function () { - sortDirectionSpy = jasmine.createSpy('sortDirection') - atom.config.onDidChange('textual-velocity.sortDirection', sortDirectionSpy) - store.dispatch(A.changeSortDirection('asc')) - - waitsFor(() => sortDirectionSpy.calls.length > 0) - }) - - it('should have updated list Height', function () { - expect(atom.config.get('textual-velocity.sortDirection')).toEqual('asc') - }) - - it('should have yielded a last action', function () { - const lastActions = store.getActions().slice(-1) - expect(lastActions[0]).toEqual(A.changeSortDirection('asc')) - }) - }) - - describe('when changed sort field', function () { - let sortFieldSpy - - beforeEach(function () { - sortFieldSpy = jasmine.createSpy('sortField') - atom.config.onDidChange('textual-velocity.sortField', sortFieldSpy) - store.dispatch(A.changeSortField('ext')) - - waitsFor(() => sortFieldSpy.calls.length > 0) - }) - - it('should have updated list Height', function () { - expect(atom.config.get('textual-velocity.sortField')).toEqual('ext') - }) - - it('should have yielded a last action', function () { - const lastActions = store.getActions().slice(-1) - expect(lastActions[0]).toEqual(A.changeSortField('ext')) - }) - }) -}) + store = mockStore(); + }); + + afterEach(function() { + epicMiddleware.replaceEpic(configChangesEpic); + }); + + it("should yield actions for initial values of config", function() { + const dispatchedActions = store.getActions(); + expect(dispatchedActions[0]).toEqual(A.changeListHeight(150)); + expect(dispatchedActions[1]).toEqual(A.changeRowHeight(20)); + expect(dispatchedActions[2]).toEqual(A.changeSortDirection("desc")); + expect(dispatchedActions[3]).toEqual(A.changeSortField("name")); + }); + + describe("when resized list action", function() { + let listHeightSpy; + + beforeEach(function() { + listHeightSpy = jasmine.createSpy("listHeight"); + atom.config.onDidChange("textual-velocity.listHeight", listHeightSpy); + store.dispatch(A.resizeList(123)); + + waitsFor(() => listHeightSpy.calls.length > 0); + }); + + it("should have updated list Height", function() { + expect(atom.config.get("textual-velocity.listHeight")).toEqual(123); + }); + + it("should have yielded a last action", function() { + const lastActions = store.getActions().slice(-2); + expect(lastActions[0]).toEqual(A.resizeList(123)); + expect(lastActions[1]).toEqual(A.changeListHeight(123)); + }); + }); + + describe("when changed row height", function() { + let rowHeightSpy; + + beforeEach(function() { + rowHeightSpy = jasmine.createSpy("rowHeight"); + atom.config.onDidChange("textual-velocity.rowHeight", rowHeightSpy); + store.dispatch(A.changeRowHeight(26)); + + waitsFor(() => rowHeightSpy.calls.length > 0); + }); + + it("should have updated list Height", function() { + expect(atom.config.get("textual-velocity.rowHeight")).toEqual(26); + }); + + it("should have yielded a last action", function() { + const lastActions = store.getActions().slice(-1); + expect(lastActions[0]).toEqual(A.changeRowHeight(26)); + }); + }); + + describe("when changed sort direction", function() { + let sortDirectionSpy; + + beforeEach(function() { + sortDirectionSpy = jasmine.createSpy("sortDirection"); + atom.config.onDidChange( + "textual-velocity.sortDirection", + sortDirectionSpy + ); + store.dispatch(A.changeSortDirection("asc")); + + waitsFor(() => sortDirectionSpy.calls.length > 0); + }); + + it("should have updated list Height", function() { + expect(atom.config.get("textual-velocity.sortDirection")).toEqual("asc"); + }); + + it("should have yielded a last action", function() { + const lastActions = store.getActions().slice(-1); + expect(lastActions[0]).toEqual(A.changeSortDirection("asc")); + }); + }); + + describe("when changed sort field", function() { + let sortFieldSpy; + + beforeEach(function() { + sortFieldSpy = jasmine.createSpy("sortField"); + atom.config.onDidChange("textual-velocity.sortField", sortFieldSpy); + store.dispatch(A.changeSortField("ext")); + + waitsFor(() => sortFieldSpy.calls.length > 0); + }); + + it("should have updated list Height", function() { + expect(atom.config.get("textual-velocity.sortField")).toEqual("ext"); + }); + + it("should have yielded a last action", function() { + const lastActions = store.getActions().slice(-1); + expect(lastActions[0]).toEqual(A.changeSortField("ext")); + }); + }); +}); diff --git a/spec/epics/file-reads-spec.js b/spec/epics/file-reads-spec.js index ef72ded..7d5e0af 100644 --- a/spec/epics/file-reads-spec.js +++ b/spec/epics/file-reads-spec.js @@ -1,31 +1,32 @@ /* @flow */ -import {createEpicMiddleware} from 'redux-observable' -import configureMockStore from 'redux-mock-store' -import FileReaders from '../../lib/file-readers' -import makeFileReadsEpic from '../../lib/epics/file-reads' -import * as A from '../../lib/action-creators' +import { createEpicMiddleware } from "redux-observable"; +import configureMockStore from "redux-mock-store"; +import FileReaders from "../../lib/file-readers"; +import makeFileReadsEpic from "../../lib/epics/file-reads"; +import * as A from "../../lib/action-creators"; -describe('epics/file-reads', () => { - let state: State - let store - let contentFileReader: FileReader +describe("epics/file-reads", () => { + let state: State; + let store; + let contentFileReader: FileReader; beforeEach(() => { - const fileReaders = new FileReaders() + const fileReaders = new FileReaders(); contentFileReader = { - notePropName: 'content', - read: (path: string, fileStats: FsStats, callback: NodeCallback) => callback(null, `content for ${path}`) - } - fileReaders.add(contentFileReader) + notePropName: "content", + read: (path: string, fileStats: FsStats, callback: NodeCallback) => + callback(null, `content for ${path}`) + }; + fileReaders.add(contentFileReader); - const fileReadsEpic = makeFileReadsEpic(fileReaders) - const epicMiddleware = createEpicMiddleware(fileReadsEpic) - const mockStore = configureMockStore([epicMiddleware]) + const fileReadsEpic = makeFileReadsEpic(fileReaders); + const epicMiddleware = createEpicMiddleware(fileReadsEpic); + const mockStore = configureMockStore([epicMiddleware]); state = { columnHeaders: [], - dir: '/notes', + dir: "/notes", editCellName: null, initialScan: { done: false, @@ -33,173 +34,202 @@ describe('epics/file-reads', () => { }, listHeight: 50, notes: {}, - queryOriginal: '', + queryOriginal: "", rowHeight: 25, scrollTop: 0, selectedNote: null, sifterResult: { items: [], options: { - fields: ['name', 'ext'], + fields: ["name", "ext"], sort: [ - {field: 'name', direction: 'asc'}, - {field: '$score', direction: 'desc'} + { field: "name", direction: "asc" }, + { field: "$score", direction: "desc" } ] }, - query: '', + query: "", tokens: [], total: 0 } - } - store = mockStore(state) - }) - - afterEach(function () { - store.dispatch(A.dispose()) // tests dispose logic working - - store.clearActions() - const finalAction = A.initialScanDone() - store.dispatch(finalAction) - expect(store.getActions()).toEqual([finalAction], 'should not have any other actions dispatched anymore') - }) - - describe('when initialScanDone action dispatches', function () { - beforeEach(function () { - const now = new Date() + }; + store = mockStore(state); + }); + + afterEach(function() { + store.dispatch(A.dispose()); // tests dispose logic working + + store.clearActions(); + const finalAction = A.initialScanDone(); + store.dispatch(finalAction); + expect(store.getActions()).toEqual( + [finalAction], + "should not have any other actions dispatched anymore" + ); + }); + + describe("when initialScanDone action dispatches", function() { + beforeEach(function() { + const now = new Date(); state.notes = { - 'nothing-changed.txt': { - content: 'has all', - ext: '.txt', - id: '1', - name: 'cached-from-prior-session', - stats: {mtime: now} + "nothing-changed.txt": { + content: "has all", + ext: ".txt", + id: "1", + name: "cached-from-prior-session", + stats: { mtime: now } }, - 'not-changed-but-missing-file-reader-value.txt': { + "not-changed-but-missing-file-reader-value.txt": { content: undefined, - ext: '.txt', - id: '1', - name: 'not-changed-but-missing-file-reader-value', - stats: {mtime: now} + ext: ".txt", + id: "1", + name: "not-changed-but-missing-file-reader-value", + stats: { mtime: now } }, - 'no-longer-existing.txt': { - ext: '.txt', - id: '2', - name: 'no-longer-existing', - stats: {mtime: new Date()} + "no-longer-existing.txt": { + ext: ".txt", + id: "2", + name: "no-longer-existing", + stats: { mtime: new Date() } }, - 'changed-file.txt': { - ext: '.txt', - id: '3', - name: 'changed-file-from-prior-session', - stats: {mtime: new Date(0)} + "changed-file.txt": { + ext: ".txt", + id: "3", + name: "changed-file-from-prior-session", + stats: { mtime: new Date(0) } } - } + }; state.initialScan = { done: true, rawFiles: [ { - filename: 'nothing-changed.txt', - stats: {mtime: now} - }, { - filename: 'alice.txt', - stats: {mtime: new Date()} - }, { - filename: 'not-changed-but-missing-file-reader-value.txt', - stats: {mtime: now} - }, { - filename: 'changed-file.txt', - stats: {mtime: new Date()} - }, { - filename: 'bob.md', - stats: {mtime: new Date()} + filename: "nothing-changed.txt", + stats: { mtime: now } + }, + { + filename: "alice.txt", + stats: { mtime: new Date() } + }, + { + filename: "not-changed-but-missing-file-reader-value.txt", + stats: { mtime: now } + }, + { + filename: "changed-file.txt", + stats: { mtime: new Date() } + }, + { + filename: "bob.md", + stats: { mtime: new Date() } } ] - } - store.dispatch(A.initialScanDone()) - waitsFor(() => store.getActions().length >= 5) - }) - - it('should dispatch a initialScanRawFilesRead action', function () { - const expectedAction = A.initialScanRawFilesRead() - const action = store.getActions().find(action => action.type === expectedAction.type) - expect(action).toEqual(expectedAction) - }) - - it('should not read a file that have not changed', function () { - const action = store.getActions().find(action => action.filename === 'nothing-changed.txt') - expect(action).toBe(undefined) - }) - - it('should read file missing value matching file reader', function () { - const action = store.getActions().find(action => action.filename === 'not-changed-but-missing-file-reader-value.txt') - expect(action).toBeDefined() - }) - - it('should read changed file', function () { - const action = store.getActions().find(action => action.filename === 'not-changed-but-missing-file-reader-value.txt') - expect(action).toBeDefined() - }) - - it('should read new files', function () { - let action - action = store.getActions().find(action => action.filename === 'alice.txt') - expect(action).toBeDefined() - action = store.getActions().find(action => action.filename === 'bob.md') - expect(action).toBeDefined() - }) - }) - - describe('when file actions dispatches', function () { - function sharedFileSpecs (action: Action) { - beforeEach(function () { - spyOn(contentFileReader, 'read').andCallThrough() - }) - - describe('when initial scan is not done yet', function () { - beforeEach(function () { - store.dispatch(action) - }) - - it('should not read any files', function () { - expect(contentFileReader.read).not.toHaveBeenCalled() - }) - }) - - describe('when initial scan is done', function () { - beforeEach(function () { - state.initialScan.done = true - store.clearActions() - store.dispatch(action) - waitsFor(() => store.getActions().length >= 1) // should have at least one more action apart from file-action - }) + }; + store.dispatch(A.initialScanDone()); + waitsFor(() => store.getActions().length >= 5); + }); + + it("should dispatch a initialScanRawFilesRead action", function() { + const expectedAction = A.initialScanRawFilesRead(); + const action = store + .getActions() + .find(action => action.type === expectedAction.type); + expect(action).toEqual(expectedAction); + }); + + it("should not read a file that have not changed", function() { + const action = store + .getActions() + .find(action => action.filename === "nothing-changed.txt"); + expect(action).toBe(undefined); + }); + + it("should read file missing value matching file reader", function() { + const action = store + .getActions() + .find( + action => + action.filename === "not-changed-but-missing-file-reader-value.txt" + ); + expect(action).toBeDefined(); + }); + + it("should read changed file", function() { + const action = store + .getActions() + .find( + action => + action.filename === "not-changed-but-missing-file-reader-value.txt" + ); + expect(action).toBeDefined(); + }); + + it("should read new files", function() { + let action; + action = store + .getActions() + .find(action => action.filename === "alice.txt"); + expect(action).toBeDefined(); + action = store.getActions().find(action => action.filename === "bob.md"); + expect(action).toBeDefined(); + }); + }); + + describe("when file actions dispatches", function() { + function sharedFileSpecs(action: Action) { + beforeEach(function() { + spyOn(contentFileReader, "read").andCallThrough(); + }); + + describe("when initial scan is not done yet", function() { + beforeEach(function() { + store.dispatch(action); + }); + + it("should not read any files", function() { + expect(contentFileReader.read).not.toHaveBeenCalled(); + }); + }); + + describe("when initial scan is done", function() { + beforeEach(function() { + state.initialScan.done = true; + store.clearActions(); + store.dispatch(action); + waitsFor(() => store.getActions().length >= 1); // should have at least one more action apart from file-action + }); + + it("should read file", function() { + expect(contentFileReader.read).toHaveBeenCalled(); + }); + + it("should dispatch a file-read action", function() { + const lastAction = store.getActions().slice(-1)[0]; + expect(lastAction).toEqual( + A.fileRead({ + filename: "alice.txt", + notePropName: "content", + value: "content for /notes/alice.txt" + }) + ); + }); + }); + } - it('should read file', function () { - expect(contentFileReader.read).toHaveBeenCalled() + describe("when file is added", function() { + sharedFileSpecs( + A.fileAdded({ + filename: "alice.txt", + stats: { mtime: new Date() } }) - - it('should dispatch a file-read action', function () { - const lastAction = store.getActions().slice(-1)[0] - expect(lastAction).toEqual(A.fileRead({ - filename: 'alice.txt', - notePropName: 'content', - value: 'content for /notes/alice.txt' - })) + ); + }); + + describe("wen file is changed", function() { + sharedFileSpecs( + A.fileChanged({ + filename: "alice.txt", + stats: { mtime: new Date() } }) - }) - } - - describe('when file is added', function () { - sharedFileSpecs(A.fileAdded({ - filename: 'alice.txt', - stats: {mtime: new Date()} - })) - }) - - describe('wen file is changed', function () { - sharedFileSpecs(A.fileChanged({ - filename: 'alice.txt', - stats: {mtime: new Date()} - })) - }) - }) -}) + ); + }); + }); +}); diff --git a/spec/epics/file-writes-spec.js b/spec/epics/file-writes-spec.js index 907fad4..62f53b2 100644 --- a/spec/epics/file-writes-spec.js +++ b/spec/epics/file-writes-spec.js @@ -1,43 +1,43 @@ /* @flow */ -import {createEpicMiddleware} from 'redux-observable' -import configureMockStore from 'redux-mock-store' -import FileWriters from '../../lib/file-writers' -import makeFileWritesEpic from '../../lib/epics/file-writes' -import * as A from '../../lib/action-creators' +import { createEpicMiddleware } from "redux-observable"; +import configureMockStore from "redux-mock-store"; +import FileWriters from "../../lib/file-writers"; +import makeFileWritesEpic from "../../lib/epics/file-writes"; +import * as A from "../../lib/action-creators"; -describe('epics/file-writes', () => { - let state: State - let store - let filenameFileWriter: FileWriter - let tagsFileWriter: FileWriter - let writeSpy +describe("epics/file-writes", () => { + let state: State; + let store; + let filenameFileWriter: FileWriter; + let tagsFileWriter: FileWriter; + let writeSpy; beforeEach(() => { - const fileWriters = new FileWriters() - writeSpy = jasmine.createSpy('write') + const fileWriters = new FileWriters(); + writeSpy = jasmine.createSpy("write"); tagsFileWriter = { - editCellName: 'nvtags', + editCellName: "nvtags", write: (path: string, str: string, callback: NodeCallback) => { - callback(new Error('this should never have been called!')) + callback(new Error("this should never have been called!")); } - } - fileWriters.add(tagsFileWriter) + }; + fileWriters.add(tagsFileWriter); filenameFileWriter = { - editCellName: 'filename', - write: function (path: string, str: string, callback: NodeCallback) { - writeSpy(...arguments) + editCellName: "filename", + write: function(path: string, str: string, callback: NodeCallback) { + writeSpy(...arguments); } - } - fileWriters.add(filenameFileWriter) + }; + fileWriters.add(filenameFileWriter); - const fileWritesEpic = makeFileWritesEpic(fileWriters) - const epicMiddleware = createEpicMiddleware(fileWritesEpic) - const mockStore = configureMockStore([epicMiddleware]) + const fileWritesEpic = makeFileWritesEpic(fileWriters); + const epicMiddleware = createEpicMiddleware(fileWritesEpic); + const mockStore = configureMockStore([epicMiddleware]); state = { columnHeaders: [], - dir: '/notes', + dir: "/notes", editCellName: null, initialScan: { done: true, @@ -45,70 +45,79 @@ describe('epics/file-writes', () => { }, listHeight: 50, notes: {}, - queryOriginal: '', + queryOriginal: "", rowHeight: 25, scrollTop: 0, selectedNote: null, sifterResult: { items: [], options: { - fields: ['name', 'ext'], + fields: ["name", "ext"], sort: [ - {field: 'name', direction: 'asc'}, - {field: '$score', direction: 'desc'} + { field: "name", direction: "asc" }, + { field: "$score", direction: "desc" } ] }, - query: '', + query: "", tokens: [], total: 0 } - } - store = mockStore(state) - }) + }; + store = mockStore(state); + }); - afterEach(function () { - store.dispatch(A.dispose()) // tests dispose logic working + afterEach(function() { + store.dispatch(A.dispose()); // tests dispose logic working - store.clearActions() - const finalAction = A.editCellSave('filename', 'should not be picked up anymore') - store.dispatch(finalAction) - expect(store.getActions()).toEqual([finalAction], 'should not have any other actions dispatched anymore') - }) + store.clearActions(); + const finalAction = A.editCellSave( + "filename", + "should not be picked up anymore" + ); + store.dispatch(finalAction); + expect(store.getActions()).toEqual( + [finalAction], + "should not have any other actions dispatched anymore" + ); + }); - describe('when saving edited cell', function () { - beforeEach(function () { - spyOn(atom.notifications, 'addError').andCallThrough() + describe("when saving edited cell", function() { + beforeEach(function() { + spyOn(atom.notifications, "addError").andCallThrough(); state.selectedNote = { - filename: 'foobar.txt', + filename: "foobar.txt", index: 1 - } - store.dispatch(A.editCellSave('filename', 'value to save')) - }) + }; + store.dispatch(A.editCellSave("filename", "value to save")); + }); - it('should try to write value to selected note', function () { - expect(writeSpy).toHaveBeenCalled() - expect(writeSpy.calls[0].args[0]).toEqual('/notes/foobar.txt') - expect(writeSpy.calls[0].args[1]).toEqual('value to save') - }) + it("should try to write value to selected note", function() { + expect(writeSpy).toHaveBeenCalled(); + expect(writeSpy.calls[0].args[0]).toEqual("/notes/foobar.txt"); + expect(writeSpy.calls[0].args[1]).toEqual("value to save"); + }); - describe('when save write succeed', function () { - beforeEach(function () { - writeSpy.calls[0].args[2](null, 'value to save') - }) + describe("when save write succeed", function() { + beforeEach(function() { + writeSpy.calls[0].args[2](null, "value to save"); + }); - it('should not add any error', function () { - expect(atom.notifications.addError).not.toHaveBeenCalled() - }) - }) + it("should not add any error", function() { + expect(atom.notifications.addError).not.toHaveBeenCalled(); + }); + }); - describe('when save write fails', function () { - beforeEach(function () { - writeSpy.calls[0].args[2](new Error('oh dear, something went wrong…'), null) - }) + describe("when save write fails", function() { + beforeEach(function() { + writeSpy.calls[0].args[2]( + new Error("oh dear, something went wrong…"), + null + ); + }); - it('should add an error explaining the situation', function () { - expect(atom.notifications.addError).toHaveBeenCalled() - }) - }) - }) -}) + it("should add an error explaining the situation", function() { + expect(atom.notifications.addError).toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/spec/epics/hidden-columns-spec.js b/spec/epics/hidden-columns-spec.js index f50405d..30d4a08 100644 --- a/spec/epics/hidden-columns-spec.js +++ b/spec/epics/hidden-columns-spec.js @@ -1,73 +1,93 @@ /* @flow */ -import {createEpicMiddleware} from 'redux-observable' -import configureMockStore from 'redux-mock-store' -import makeHiddenColumnsEpic from '../../lib/epics/hidden-columns' -import Columns from '../../lib/columns' -import SummaryColumn from '../../lib/columns/summary-column' -import FileIconColumn from '../../lib/columns/file-icon-column' -import * as A from '../../lib/action-creators' +import { createEpicMiddleware } from "redux-observable"; +import configureMockStore from "redux-mock-store"; +import makeHiddenColumnsEpic from "../../lib/epics/hidden-columns"; +import Columns from "../../lib/columns"; +import SummaryColumn from "../../lib/columns/summary-column"; +import FileIconColumn from "../../lib/columns/file-icon-column"; +import * as A from "../../lib/action-creators"; -describe('epics/hidden-columns', () => { - let store - let workspaceElement - let contextMenuItems - let contextMenuItemsLabels +describe("epics/hidden-columns", () => { + let store; + let workspaceElement; + let contextMenuItems; + let contextMenuItemsLabels; beforeEach(() => { - workspaceElement = atom.views.getView(atom.workspace) - jasmine.attachToDOM(workspaceElement) + workspaceElement = atom.views.getView(atom.workspace); + jasmine.attachToDOM(workspaceElement); - const columns = new Columns() - columns.add(new SummaryColumn({sortField: 'name', editCellName: ''})) - columns.add(new FileIconColumn({sortField: 'ext'})) + const columns = new Columns(); + columns.add(new SummaryColumn({ sortField: "name", editCellName: "" })); + columns.add(new FileIconColumn({ sortField: "ext" })); - spyOn(atom.contextMenu, 'add').andCallThrough() + spyOn(atom.contextMenu, "add").andCallThrough(); - const hiddenColumnsEpic = makeHiddenColumnsEpic(columns) - const epicMiddleware = createEpicMiddleware(hiddenColumnsEpic) - const mockStore = configureMockStore([epicMiddleware]) + const hiddenColumnsEpic = makeHiddenColumnsEpic(columns); + const epicMiddleware = createEpicMiddleware(hiddenColumnsEpic); + const mockStore = configureMockStore([epicMiddleware]); - contextMenuItems = () => atom.contextMenu.add.calls.slice(-1)[0].args[0]['.textual-velocity .header'] - contextMenuItemsLabels = () => contextMenuItems().map(item => item['label']) + contextMenuItems = () => + atom.contextMenu.add.calls.slice(-1)[0].args[0][ + ".textual-velocity .header" + ]; + contextMenuItemsLabels = () => + contextMenuItems().map(item => item["label"]); - store = mockStore() - }) + store = mockStore(); + }); - it('should yield actions for initial hidden columns', function () { - const dispatchedActions = store.getActions() - expect(dispatchedActions[0]).toEqual(A.changeHiddenColumns([])) - }) + it("should yield actions for initial hidden columns", function() { + const dispatchedActions = store.getActions(); + expect(dispatchedActions[0]).toEqual(A.changeHiddenColumns([])); + }); - it('should register a contextmenu actions', function () { - expect(atom.contextMenu.add).toHaveBeenCalled() + it("should register a contextmenu actions", function() { + expect(atom.contextMenu.add).toHaveBeenCalled(); - expect(contextMenuItemsLabels()).toEqual(['✓ Summary', '✓ File type']) + expect(contextMenuItemsLabels()).toEqual(["✓ Summary", "✓ File type"]); - const contextMenuCommands = contextMenuItems().map(item => item['command']) + const contextMenuCommands = contextMenuItems().map(item => item["command"]); const registeredCommands = atom.commands - .findCommands({target: workspaceElement}) - .filter(cmd => cmd.name.startsWith('textual-velocity')) - .map(cmd => cmd.name) - expect(registeredCommands).toEqual(contextMenuCommands) - }) + .findCommands({ target: workspaceElement }) + .filter(cmd => cmd.name.startsWith("textual-velocity")) + .map(cmd => cmd.name); + expect(registeredCommands).toEqual(contextMenuCommands); + }); - describe('when toggling a column command', function () { - it('should update hidden columns and labels', function () { - store.clearActions() - atom.commands.dispatch(workspaceElement, 'textual-velocity:toggle-summary-column') - expect(store.getActions()[0]).toEqual(A.changeHiddenColumns(['summary'])) - expect(contextMenuItemsLabels()).toEqual([' Summary', '✓ File type']) + describe("when toggling a column command", function() { + it("should update hidden columns and labels", function() { + store.clearActions(); + atom.commands.dispatch( + workspaceElement, + "textual-velocity:toggle-summary-column" + ); + expect(store.getActions()[0]).toEqual(A.changeHiddenColumns(["summary"])); + expect(contextMenuItemsLabels()).toEqual([" Summary", "✓ File type"]); - store.clearActions() - atom.commands.dispatch(workspaceElement, 'textual-velocity:toggle-file-type-column') - expect(store.getActions()[0]).toEqual(A.changeHiddenColumns(['summary', 'file-type'])) - expect(contextMenuItemsLabels()).toEqual([' Summary', ' File type']) + store.clearActions(); + atom.commands.dispatch( + workspaceElement, + "textual-velocity:toggle-file-type-column" + ); + expect(store.getActions()[0]).toEqual( + A.changeHiddenColumns(["summary", "file-type"]) + ); + expect(contextMenuItemsLabels()).toEqual([ + " Summary", + " File type" + ]); - store.clearActions() - atom.commands.dispatch(workspaceElement, 'textual-velocity:toggle-summary-column') - expect(store.getActions()[0]).toEqual(A.changeHiddenColumns(['file-type'])) - expect(contextMenuItemsLabels()).toEqual(['✓ Summary', ' File type']) - }) - }) -}) + store.clearActions(); + atom.commands.dispatch( + workspaceElement, + "textual-velocity:toggle-summary-column" + ); + expect(store.getActions()[0]).toEqual( + A.changeHiddenColumns(["file-type"]) + ); + expect(contextMenuItemsLabels()).toEqual(["✓ Summary", " File type"]); + }); + }); +}); diff --git a/spec/epics/path-watcher-spec.js b/spec/epics/path-watcher-spec.js index 13bc6da..e8da052 100644 --- a/spec/epics/path-watcher-spec.js +++ b/spec/epics/path-watcher-spec.js @@ -1,135 +1,137 @@ /* @flow */ -import fs from 'fs' -import Path from 'path' -import temp from 'temp' -import {createEpicMiddleware} from 'redux-observable' -import configureMockStore from 'redux-mock-store' -import pathWatcherEpic from '../../lib/epics/path-watcher' -import * as A from '../../lib/action-creators' +import fs from "fs"; +import Path from "path"; +import temp from "temp"; +import { createEpicMiddleware } from "redux-observable"; +import configureMockStore from "redux-mock-store"; +import pathWatcherEpic from "../../lib/epics/path-watcher"; +import * as A from "../../lib/action-creators"; -temp.track() +temp.track(); -const epicMiddleware = createEpicMiddleware(pathWatcherEpic) -const mockStore = configureMockStore([epicMiddleware]) +const epicMiddleware = createEpicMiddleware(pathWatcherEpic); +const mockStore = configureMockStore([epicMiddleware]); -describe('epics/path-watcher', () => { - let dir, store +describe("epics/path-watcher", () => { + let dir, store; beforeEach(() => { - jasmine.useRealClock() // required for chokidar timers to work! e.g. atomic unlink events + jasmine.useRealClock(); // required for chokidar timers to work! e.g. atomic unlink events - const tempDirPath = temp.mkdirSync('empty-dir') - dir = fs.realpathSync(tempDirPath) + const tempDirPath = temp.mkdirSync("empty-dir"); + dir = fs.realpathSync(tempDirPath); - fs.writeFileSync(Path.join(dir, 'note-1.txt'), '1') - fs.writeFileSync(Path.join(dir, 'note-2.txt'), '2') - fs.writeFileSync(Path.join(dir, 'other.zip'), '...') - fs.writeFileSync(Path.join(dir, 'note-3.txt'), '3') + fs.writeFileSync(Path.join(dir, "note-1.txt"), "1"); + fs.writeFileSync(Path.join(dir, "note-2.txt"), "2"); + fs.writeFileSync(Path.join(dir, "other.zip"), "..."); + fs.writeFileSync(Path.join(dir, "note-3.txt"), "3"); - atom.config.set('textual-velocity.ignoredNames', ['.DS_Store']) - store = mockStore({dir}) - }) + atom.config.set("textual-velocity.ignoredNames", [".DS_Store"]); + store = mockStore({ dir }); + }); - afterEach(function () { - store.dispatch(A.dispose()) // should terminate any running processes - epicMiddleware.replaceEpic(pathWatcherEpic) - temp.cleanupSync() - }) + afterEach(function() { + store.dispatch(A.dispose()); // should terminate any running processes + epicMiddleware.replaceEpic(pathWatcherEpic); + temp.cleanupSync(); + }); - describe('when start-initial-scan action is triggered', function () { - beforeEach(function () { - store.dispatch(A.startInitialScan()) + describe("when start-initial-scan action is triggered", function() { + beforeEach(function() { + store.dispatch(A.startInitialScan()); // wait for initial scan to be done (i.e. the last expected action), implicitly verifies that to work, too - waitsFor(() => store.getActions().slice(-1)[0].type === A.INITIAL_SCAN_DONE) - }) - - it('should have yielded file-added actions for each file', function () { - expect(store.getActions().length).toEqual(5) - - const [, action] = store.getActions() - expect(action.type).toEqual(A.FILE_ADDED) - expect(action.rawFile).toEqual(jasmine.any(Object)) - expect(action.rawFile.filename).toMatch(/note-\d\.txt/) - }) - - it('should have converted stats strings to date object', function () { - const [, action] = store.getActions() - expect(action.rawFile.stats).toEqual(jasmine.any(Object)) - expect(action.rawFile.stats.atime).toEqual(jasmine.any(Date)) - expect(action.rawFile.stats.birthtime).toEqual(jasmine.any(Date)) - expect(action.rawFile.stats.ctime).toEqual(jasmine.any(Date)) - expect(action.rawFile.stats.mtime).toEqual(jasmine.any(Date)) - }) - - describe('when a new file is created', function () { - beforeEach(function () { - store.clearActions() - fs.writeFileSync(Path.join(dir, 'note-4.txt'), '4') - waitsFor(() => store.getActions().length >= 1) - }) - - it('should yield a file-added action', function () { - expect(store.getActions()[0].type).toEqual(A.FILE_ADDED) - }) - - it('should have a rawFile on action', function () { - const rawFile = store.getActions()[0].rawFile - expect(rawFile).toEqual(jasmine.any(Object)) - expect(rawFile.filename).toEqual('note-4.txt') - - expect(rawFile.stats).toEqual(jasmine.any(Object)) - expect(rawFile.stats.atime).toEqual(jasmine.any(Date)) - expect(rawFile.stats.birthtime).toEqual(jasmine.any(Date)) - expect(rawFile.stats.ctime).toEqual(jasmine.any(Date)) - expect(rawFile.stats.mtime).toEqual(jasmine.any(Date)) - }) - }) - - describe('when change file', function () { - beforeEach(function () { - store.clearActions() - fs.writeFileSync(Path.join(dir, 'note-1.txt'), '111') - waitsFor(() => store.getActions().length >= 1) - }) - - it('should yield a file-changed action', function () { - expect(store.getActions()[0].type).toEqual(A.FILE_CHANGED) - }) - - it('should have a rawFile on action', function () { - const rawFile = store.getActions()[0].rawFile - expect(rawFile).toEqual(jasmine.any(Object)) - expect(rawFile.filename).toEqual('note-1.txt') - - expect(rawFile.stats).toEqual(jasmine.any(Object)) - expect(rawFile.stats.atime).toEqual(jasmine.any(Date)) - expect(rawFile.stats.birthtime).toEqual(jasmine.any(Date)) - expect(rawFile.stats.ctime).toEqual(jasmine.any(Date)) - expect(rawFile.stats.mtime).toEqual(jasmine.any(Date)) - }) - }) - - describe('when delete file', function () { - beforeEach(function () { - store.clearActions() - fs.unlinkSync(Path.join(dir, 'note-1.txt')) - - let done = false - fs.unlink(Path.join(dir, 'note-3.txt'), (err, result) => { - if (err) console.error(err) // get more info in case of error - done = true - }) - waitsFor(() => done) - - waitsFor(() => store.getActions().length >= 1) - }) - - it('should yield a unlink action with deleted filename', function () { - expect(store.getActions()[0].type).toEqual(A.FILE_DELETED) - expect(store.getActions()[0].filename).toMatch(/^note/) - }) - }) - }) -}) + waitsFor( + () => store.getActions().slice(-1)[0].type === A.INITIAL_SCAN_DONE + ); + }); + + it("should have yielded file-added actions for each file", function() { + expect(store.getActions().length).toEqual(5); + + const [, action] = store.getActions(); + expect(action.type).toEqual(A.FILE_ADDED); + expect(action.rawFile).toEqual(jasmine.any(Object)); + expect(action.rawFile.filename).toMatch(/note-\d\.txt/); + }); + + it("should have converted stats strings to date object", function() { + const [, action] = store.getActions(); + expect(action.rawFile.stats).toEqual(jasmine.any(Object)); + expect(action.rawFile.stats.atime).toEqual(jasmine.any(Date)); + expect(action.rawFile.stats.birthtime).toEqual(jasmine.any(Date)); + expect(action.rawFile.stats.ctime).toEqual(jasmine.any(Date)); + expect(action.rawFile.stats.mtime).toEqual(jasmine.any(Date)); + }); + + describe("when a new file is created", function() { + beforeEach(function() { + store.clearActions(); + fs.writeFileSync(Path.join(dir, "note-4.txt"), "4"); + waitsFor(() => store.getActions().length >= 1); + }); + + it("should yield a file-added action", function() { + expect(store.getActions()[0].type).toEqual(A.FILE_ADDED); + }); + + it("should have a rawFile on action", function() { + const rawFile = store.getActions()[0].rawFile; + expect(rawFile).toEqual(jasmine.any(Object)); + expect(rawFile.filename).toEqual("note-4.txt"); + + expect(rawFile.stats).toEqual(jasmine.any(Object)); + expect(rawFile.stats.atime).toEqual(jasmine.any(Date)); + expect(rawFile.stats.birthtime).toEqual(jasmine.any(Date)); + expect(rawFile.stats.ctime).toEqual(jasmine.any(Date)); + expect(rawFile.stats.mtime).toEqual(jasmine.any(Date)); + }); + }); + + describe("when change file", function() { + beforeEach(function() { + store.clearActions(); + fs.writeFileSync(Path.join(dir, "note-1.txt"), "111"); + waitsFor(() => store.getActions().length >= 1); + }); + + it("should yield a file-changed action", function() { + expect(store.getActions()[0].type).toEqual(A.FILE_CHANGED); + }); + + it("should have a rawFile on action", function() { + const rawFile = store.getActions()[0].rawFile; + expect(rawFile).toEqual(jasmine.any(Object)); + expect(rawFile.filename).toEqual("note-1.txt"); + + expect(rawFile.stats).toEqual(jasmine.any(Object)); + expect(rawFile.stats.atime).toEqual(jasmine.any(Date)); + expect(rawFile.stats.birthtime).toEqual(jasmine.any(Date)); + expect(rawFile.stats.ctime).toEqual(jasmine.any(Date)); + expect(rawFile.stats.mtime).toEqual(jasmine.any(Date)); + }); + }); + + describe("when delete file", function() { + beforeEach(function() { + store.clearActions(); + fs.unlinkSync(Path.join(dir, "note-1.txt")); + + let done = false; + fs.unlink(Path.join(dir, "note-3.txt"), (err, result) => { + if (err) console.error(err); // get more info in case of error + done = true; + }); + waitsFor(() => done); + + waitsFor(() => store.getActions().length >= 1); + }); + + it("should yield a unlink action with deleted filename", function() { + expect(store.getActions()[0].type).toEqual(A.FILE_DELETED); + expect(store.getActions()[0].filename).toMatch(/^note/); + }); + }); + }); +}); diff --git a/spec/epics/preview-note-spec.js b/spec/epics/preview-note-spec.js index aebaf3b..98fbcb5 100644 --- a/spec/epics/preview-note-spec.js +++ b/spec/epics/preview-note-spec.js @@ -1,21 +1,21 @@ /* @flow */ -import {createEpicMiddleware} from 'redux-observable' -import configureMockStore from 'redux-mock-store' -import previewNoteEpic from '../../lib/epics/preview-note' -import * as A from '../../lib/action-creators' +import { createEpicMiddleware } from "redux-observable"; +import configureMockStore from "redux-mock-store"; +import previewNoteEpic from "../../lib/epics/preview-note"; +import * as A from "../../lib/action-creators"; -describe('epics/preview-note', () => { - let state: State - let store - let mockStore +describe("epics/preview-note", () => { + let state: State; + let store; + let mockStore; beforeEach(() => { - jasmine.useRealClock() - jasmine.Clock.useMock() + jasmine.useRealClock(); + jasmine.Clock.useMock(); state = { columnHeaders: [], - dir: '/notes', + dir: "/notes", editCellName: null, initialScan: { done: false, @@ -23,189 +23,199 @@ describe('epics/preview-note', () => { }, listHeight: 75, notes: { - 'alice.txt': { - id: '0', - name: 'alice', - ext: 'txt', - stats: {mtime: new Date()} + "alice.txt": { + id: "0", + name: "alice", + ext: "txt", + stats: { mtime: new Date() } }, - 'bob.md': { - id: '1', - name: 'bob', - ext: 'md', - stats: {mtime: new Date()} + "bob.md": { + id: "1", + name: "bob", + ext: "md", + stats: { mtime: new Date() } }, - 'cesar.txt': { - id: '2', - name: 'cesar', - ext: 'txt', - stats: {mtime: new Date()} + "cesar.txt": { + id: "2", + name: "cesar", + ext: "txt", + stats: { mtime: new Date() } } }, - queryOriginal: '', + queryOriginal: "", rowHeight: 25, scrollTop: 0, selectedNote: null, sifterResult: { items: [ - {id: 'alice.txt', score: 1.0}, - {id: 'bob.md', score: 0.9}, - {id: 'cesar.txt', score: 0.8} + { id: "alice.txt", score: 1.0 }, + { id: "bob.md", score: 0.9 }, + { id: "cesar.txt", score: 0.8 } ], options: { - fields: ['name', 'ext'], + fields: ["name", "ext"], sort: [ - {field: 'name', direction: 'asc'}, - {field: '$score', direction: 'desc'} + { field: "name", direction: "asc" }, + { field: "$score", direction: "desc" } ] }, - query: '', + query: "", tokens: [], total: 3 } - } + }; - const getState = () => ({...state}) // make sure state is unique for each action + const getState = () => ({ ...state }); // make sure state is unique for each action - const epicMiddleware = createEpicMiddleware(previewNoteEpic) - mockStore = configureMockStore([epicMiddleware]) - store = mockStore(getState) - }) + const epicMiddleware = createEpicMiddleware(previewNoteEpic); + mockStore = configureMockStore([epicMiddleware]); + store = mockStore(getState); + }); - afterEach(function () { - store.dispatch(A.dispose()) - jasmine.Clock.tick(500) + afterEach(function() { + store.dispatch(A.dispose()); + jasmine.Clock.tick(500); // clean workspace state - let count = 0 + let count = 0; while (atom.workspace.getPaneItems().length) { - atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow() + atom.workspace.closeActivePaneItemOrEmptyPaneOrWindow(); if (count === 50) { - throw new Error('infinite loop!') + throw new Error("infinite loop!"); } - count++ + count++; } - }) + }); - describe('when select note', function () { - describe('when there is no editor for path', function () { - beforeEach(function () { + describe("when select note", function() { + describe("when there is no editor for path", function() { + beforeEach(function() { state.selectedNote = { - filename: 'alice.txt', + filename: "alice.txt", index: 0 - } - store.dispatch(A.selectNext()) - jasmine.Clock.tick(500) - - waitsFor(() => atom.workspace.getPaneItems().length > 0) // waits for preview - }) - - it('should open preview', function () { - expect(atom.workspace.getPaneItems()[0].tagName.toLowerCase()).toContain('preview') - }) - - it('should close preview when deselected note', function () { - state.selectedNote = null - store.dispatch(A.resetSearch()) - jasmine.Clock.tick(500) - - waitsFor(() => atom.workspace.getPaneItems().length === 0) - }) - - describe('when click preview', function () { - beforeEach(function () { - atom.workspace.getPaneItems()[0].click() - waitsFor(() => !atom.workspace.getPaneItems()[0].tagName) - }) - - it('should open editor for given preview', function () { - expect(atom.workspace.getPaneItems()[0].getPath()).toEqual(jasmine.any(String)) - }) - }) - - describe('when dispose action', function () { - beforeEach(function () { - store.dispatch(A.dispose()) - jasmine.Clock.tick(500) - }) - - it('should dispose elements and no longer open any previews', function () { + }; + store.dispatch(A.selectNext()); + jasmine.Clock.tick(500); + + waitsFor(() => atom.workspace.getPaneItems().length > 0); // waits for preview + }); + + it("should open preview", function() { + expect( + atom.workspace.getPaneItems()[0].tagName.toLowerCase() + ).toContain("preview"); + }); + + it("should close preview when deselected note", function() { + state.selectedNote = null; + store.dispatch(A.resetSearch()); + jasmine.Clock.tick(500); + + waitsFor(() => atom.workspace.getPaneItems().length === 0); + }); + + describe("when click preview", function() { + beforeEach(function() { + atom.workspace.getPaneItems()[0].click(); + waitsFor(() => !atom.workspace.getPaneItems()[0].tagName); + }); + + it("should open editor for given preview", function() { + expect(atom.workspace.getPaneItems()[0].getPath()).toEqual( + jasmine.any(String) + ); + }); + }); + + describe("when dispose action", function() { + beforeEach(function() { + store.dispatch(A.dispose()); + jasmine.Clock.tick(500); + }); + + it("should dispose elements and no longer open any previews", function() { state.selectedNote = { - filename: 'alice.txt', + filename: "alice.txt", index: 0 - } - store.dispatch(A.selectNext()) - jasmine.Clock.tick(500) - - expect(atom.workspace.getPaneItems().length).toEqual(0) - }) - }) - }) - - describe('when a text editor for matching path is already open', function () { - beforeEach(function () { - atom.workspace.open('/notes/bob.md') + }; + store.dispatch(A.selectNext()); + jasmine.Clock.tick(500); + + expect(atom.workspace.getPaneItems().length).toEqual(0); + }); + }); + }); + + describe("when a text editor for matching path is already open", function() { + beforeEach(function() { + atom.workspace.open("/notes/bob.md"); state.selectedNote = { - filename: 'alice.txt', + filename: "alice.txt", index: 0 - } - store.dispatch(A.selectNext()) - jasmine.Clock.tick(500) + }; + store.dispatch(A.selectNext()); + jasmine.Clock.tick(500); - waitsFor(() => atom.workspace.getPaneItems().length === 2) + waitsFor(() => atom.workspace.getPaneItems().length === 2); runs(() => { state.selectedNote = { - filename: 'bob.md', + filename: "bob.md", index: 1 - } - store.dispatch(A.selectNext()) - jasmine.Clock.tick(500) - }) - waitsFor(() => atom.workspace.getPaneItems().length === 1) // should close the preview - }) - - it('should reuse text editor as preview', function () { - expect(atom.workspace.getPaneItems()[0].tagName).toBe(undefined) // not a preview - }) - }) - }) - - describe('when open-note action', function () { - describe('when there is no selected note', function () { - beforeEach(function () { - store.dispatch(A.openNote()) - jasmine.Clock.tick(500) - - atom.config.set('textual-velocity.defaultExt', 'abc') - store.dispatch(A.openNote()) - jasmine.Clock.tick(500) - - waitsFor(() => atom.workspace.getPaneItems().length >= 2) - }) - - it('should open a new untitled file', function () { - expect(atom.workspace.getPaneItems()[0].getPath()).toEqual('/notes/untitled.md') - }) - - it('should allow override defaut extension', function () { - expect(atom.workspace.getPaneItems()[1].getPath()).toEqual('/notes/untitled.abc') - }) - }) - - describe('when there is a selected note', function () { - beforeEach(function () { + }; + store.dispatch(A.selectNext()); + jasmine.Clock.tick(500); + }); + waitsFor(() => atom.workspace.getPaneItems().length === 1); // should close the preview + }); + + it("should reuse text editor as preview", function() { + expect(atom.workspace.getPaneItems()[0].tagName).toBe(undefined); // not a preview + }); + }); + }); + + describe("when open-note action", function() { + describe("when there is no selected note", function() { + beforeEach(function() { + store.dispatch(A.openNote()); + jasmine.Clock.tick(500); + + atom.config.set("textual-velocity.defaultExt", "abc"); + store.dispatch(A.openNote()); + jasmine.Clock.tick(500); + + waitsFor(() => atom.workspace.getPaneItems().length >= 2); + }); + + it("should open a new untitled file", function() { + expect(atom.workspace.getPaneItems()[0].getPath()).toEqual( + "/notes/untitled.md" + ); + }); + + it("should allow override defaut extension", function() { + expect(atom.workspace.getPaneItems()[1].getPath()).toEqual( + "/notes/untitled.abc" + ); + }); + }); + + describe("when there is a selected note", function() { + beforeEach(function() { state.selectedNote = { index: 0, - filename: 'alice.txt' - } - store = mockStore(state) - store.dispatch(A.openNote()) - waitsFor(() => atom.workspace.getPaneItems().length >= 2) - }) - - it('should open path of selected note', function () { - expect(atom.workspace.getPaneItems()[0].getPath()).toEqual('/notes/alice.txt') - }) - }) - }) -}) + filename: "alice.txt" + }; + store = mockStore(state); + store.dispatch(A.openNote()); + waitsFor(() => atom.workspace.getPaneItems().length >= 2); + }); + + it("should open path of selected note", function() { + expect(atom.workspace.getPaneItems()[0].getPath()).toEqual( + "/notes/alice.txt" + ); + }); + }); + }); +}); diff --git a/spec/fields/parsed-path-field-spec.js b/spec/fields/parsed-path-field-spec.js index 8aadb7f..2bd2845 100644 --- a/spec/fields/parsed-path-field-spec.js +++ b/spec/fields/parsed-path-field-spec.js @@ -1,29 +1,32 @@ -'use babel' +"use babel"; -import ParsedPathField from '../../lib/fields/parsed-path-field' +import ParsedPathField from "../../lib/fields/parsed-path-field"; -describe('fields/parsed-path-field', function () { - let field +describe("fields/parsed-path-field", function() { + let field; - beforeEach(function () { - field = new ParsedPathField({notePropName: 'filename', parsedPathPropName: 'name'}) - }) + beforeEach(function() { + field = new ParsedPathField({ + notePropName: "filename", + parsedPathPropName: "name" + }); + }); - describe('.notePropName', function () { - it('should return given name', function () { - expect(field.notePropName).toEqual('filename') - }) - }) + describe(".notePropName", function() { + it("should return given name", function() { + expect(field.notePropName).toEqual("filename"); + }); + }); - describe('.value', function () { - let note: any + describe(".value", function() { + let note: any; - it('should return the normalized parsed path piece', function () { - expect(field.value(note, 'snowflake.md')).toEqual('snowflake') - }) + it("should return the normalized parsed path piece", function() { + expect(field.value(note, "snowflake.md")).toEqual("snowflake"); + }); - it('should normalize unicode codepoints to match keyboard input', function () { - expect(field.value(note, '\u0061\u0308lg.md')).toEqual('älg') - }) - }) -}) + it("should normalize unicode codepoints to match keyboard input", function() { + expect(field.value(note, "\u0061\u0308lg.md")).toEqual("älg"); + }); + }); +}); diff --git a/spec/fields/stats-date-field-spec.js b/spec/fields/stats-date-field-spec.js index 8906e74..3d906ad 100644 --- a/spec/fields/stats-date-field-spec.js +++ b/spec/fields/stats-date-field-spec.js @@ -1,25 +1,36 @@ -'use babel' +"use babel"; -import StatsDateField from '../../lib/fields/stats-date-field' +import StatsDateField from "../../lib/fields/stats-date-field"; -describe('fields/stats-date-field', function () { - let field +describe("fields/stats-date-field", function() { + let field; - describe('.notePropName', function () { - it('should return given name', function () { - field = new StatsDateField({notePropName: 'a-name', statsPropName: 'birthtime'}) - expect(field.notePropName).toEqual('a-name') - }) - }) + describe(".notePropName", function() { + it("should return given name", function() { + field = new StatsDateField({ + notePropName: "a-name", + statsPropName: "birthtime" + }); + expect(field.notePropName).toEqual("a-name"); + }); + }); - describe('.value', function () { - it('should return the value of the given prop path', function () { - field = new StatsDateField({notePropName: 'a-name', statsPropName: 'birthtime'}) - expect(field.value({stats: {birthtime: new Date()}})).toEqual(jasmine.any(Number)) - expect(field.value({stats: {other: new Date()}})).toBeUndefined() + describe(".value", function() { + it("should return the value of the given prop path", function() { + field = new StatsDateField({ + notePropName: "a-name", + statsPropName: "birthtime" + }); + expect(field.value({ stats: { birthtime: new Date() } })).toEqual( + jasmine.any(Number) + ); + expect(field.value({ stats: { other: new Date() } })).toBeUndefined(); - field = new StatsDateField({notePropName: 'a-name', statsPropName: 'other'}) - expect(field.value({stats: {birthtime: new Date()}})).toBeUndefined() - }) - }) -}) + field = new StatsDateField({ + notePropName: "a-name", + statsPropName: "other" + }); + expect(field.value({ stats: { birthtime: new Date() } })).toBeUndefined(); + }); + }); +}); diff --git a/spec/get-valid-dir-from-path-spec.js b/spec/get-valid-dir-from-path-spec.js index 5c78a6b..d109fd4 100644 --- a/spec/get-valid-dir-from-path-spec.js +++ b/spec/get-valid-dir-from-path-spec.js @@ -1,42 +1,42 @@ /* @flow */ -import getValidDirFromPath from '../lib/get-valid-dir-from-path' +import getValidDirFromPath from "../lib/get-valid-dir-from-path"; const assertCommons = dir => { - expect(dir).toEqual(jasmine.any(String)) - expect(dir).not.toEqual('') -} - -describe('get-valid-dir-from-path', function () { - let dir - - it('should return default path if no path is set', function () { - dir = getValidDirFromPath('') - assertCommons(dir) - expect(dir).toMatch(/.+notes$/) - - dir = getValidDirFromPath(' ') - assertCommons(dir) - expect(dir).toMatch(/.+notes$/) - }) - - it("should expand any relative path dir to absoute dir in user's home dir", function () { - const dir = getValidDirFromPath(' custom-path/ ') - assertCommons(dir) - expect(dir).toMatch(/.+custom-path$/) - expect(dir).not.toEqual('custom-path') - }) - - it('should return absolute path as is', function () { - const dir = getValidDirFromPath(' /Users/alice/notes/ ') - assertCommons(dir) - expect(dir).toEqual('/Users/alice/notes') - }) - - it('should expand home shortcut to absolute path', function () { - const dir = getValidDirFromPath(' ~/home-notes/ ') - assertCommons(dir) - expect(dir).toMatch(/.+home-notes$/) - expect(dir).not.toEqual('~/home-notes') - }) -}) + expect(dir).toEqual(jasmine.any(String)); + expect(dir).not.toEqual(""); +}; + +describe("get-valid-dir-from-path", function() { + let dir; + + it("should return default path if no path is set", function() { + dir = getValidDirFromPath(""); + assertCommons(dir); + expect(dir).toMatch(/.+notes$/); + + dir = getValidDirFromPath(" "); + assertCommons(dir); + expect(dir).toMatch(/.+notes$/); + }); + + it("should expand any relative path dir to absoute dir in user's home dir", function() { + const dir = getValidDirFromPath(" custom-path/ "); + assertCommons(dir); + expect(dir).toMatch(/.+custom-path$/); + expect(dir).not.toEqual("custom-path"); + }); + + it("should return absolute path as is", function() { + const dir = getValidDirFromPath(" /Users/alice/notes/ "); + assertCommons(dir); + expect(dir).toEqual("/Users/alice/notes"); + }); + + it("should expand home shortcut to absolute path", function() { + const dir = getValidDirFromPath(" ~/home-notes/ "); + assertCommons(dir); + expect(dir).toMatch(/.+home-notes$/); + expect(dir).not.toEqual("~/home-notes"); + }); +}); diff --git a/spec/initial-scan-task-spec.js b/spec/initial-scan-task-spec.js index 4d77e6e..06996a1 100644 --- a/spec/initial-scan-task-spec.js +++ b/spec/initial-scan-task-spec.js @@ -1,55 +1,55 @@ /* @flow */ -import {Task} from 'atom' -import fs from 'fs' -import Path from 'path' -import temp from 'temp' - -temp.track() - -describe('initial-scan-task', () => { - let task, dir, addSpy, changeSpy, unlinkSpy - - beforeEach(function () { - const tempDirPath = temp.mkdirSync('empty-dir') - dir = fs.realpathSync(tempDirPath) - - fs.writeFileSync(Path.join(dir, 'note-1.txt'), '1') - fs.writeFileSync(Path.join(dir, 'note-2.txt'), '2') - fs.writeFileSync(Path.join(dir, 'other.zip'), '...') - fs.writeFileSync(Path.join(dir, 'note-3.txt'), '3') - - const taskPath = Path.join(__dirname, '..', 'lib', 'initial-scan-task.js') - task = new Task(taskPath) - - addSpy = jasmine.createSpy('add') - changeSpy = jasmine.createSpy('change') - unlinkSpy = jasmine.createSpy('unlink') - task.on('add', addSpy) - task.on('change', changeSpy) - task.on('unlink', unlinkSpy) - - let done = false - task.on('ready', () => { - done = true - }) +import { Task } from "atom"; +import fs from "fs"; +import Path from "path"; +import temp from "temp"; + +temp.track(); + +describe("initial-scan-task", () => { + let task, dir, addSpy, changeSpy, unlinkSpy; + + beforeEach(function() { + const tempDirPath = temp.mkdirSync("empty-dir"); + dir = fs.realpathSync(tempDirPath); + + fs.writeFileSync(Path.join(dir, "note-1.txt"), "1"); + fs.writeFileSync(Path.join(dir, "note-2.txt"), "2"); + fs.writeFileSync(Path.join(dir, "other.zip"), "..."); + fs.writeFileSync(Path.join(dir, "note-3.txt"), "3"); + + const taskPath = Path.join(__dirname, "..", "lib", "initial-scan-task.js"); + task = new Task(taskPath); + + addSpy = jasmine.createSpy("add"); + changeSpy = jasmine.createSpy("change"); + unlinkSpy = jasmine.createSpy("unlink"); + task.on("add", addSpy); + task.on("change", changeSpy); + task.on("unlink", unlinkSpy); + + let done = false; + task.on("ready", () => { + done = true; + }); const chokidarOptions: ChokidarOptions = { cwd: dir - } - task.start(chokidarOptions) - waitsFor(() => done) // implicitly test INITIAL_SCAN_DONE - }) - - afterEach(function () { - temp.cleanupSync() - task.send('dispose') - task.terminate() - }) - - it('should yield add events for each file found in dir', function () { - expect(addSpy).toHaveBeenCalled() - expect(addSpy.calls.length).toEqual(4) - expect(addSpy.calls[0].args[0].filename).toEqual(jasmine.any(String)) - expect(addSpy.calls[0].args[0].stats).toEqual(jasmine.any(Object)) - }) -}) + }; + task.start(chokidarOptions); + waitsFor(() => done); // implicitly test INITIAL_SCAN_DONE + }); + + afterEach(function() { + temp.cleanupSync(); + task.send("dispose"); + task.terminate(); + }); + + it("should yield add events for each file found in dir", function() { + expect(addSpy).toHaveBeenCalled(); + expect(addSpy.calls.length).toEqual(4); + expect(addSpy.calls[0].args[0].filename).toEqual(jasmine.any(String)); + expect(addSpy.calls[0].args[0].stats).toEqual(jasmine.any(Object)); + }); +}); diff --git a/spec/main-spec.js b/spec/main-spec.js index 8c187c9..46d8ee3 100644 --- a/spec/main-spec.js +++ b/spec/main-spec.js @@ -1,91 +1,97 @@ -'use babel' +"use babel"; /* global CustomEvent */ -import Path from 'path' +import Path from "path"; -describe('main', () => { - beforeEach(function () { - jasmine.useRealClock() - this.workspaceElement = atom.views.getView(atom.workspace) - jasmine.attachToDOM(this.workspaceElement) +describe("main", () => { + beforeEach(function() { + jasmine.useRealClock(); + this.workspaceElement = atom.views.getView(atom.workspace); + jasmine.attachToDOM(this.workspaceElement); - atom.config.set('textual-velocity.path', __dirname) // ./spec + atom.config.set("textual-velocity.path", __dirname); // ./spec // Spy on fatal notifications to extract activationErroror, to re-throw it here - spyOn(atom.notifications, 'addFatalError').andCallFake((msg, d) => { - const err = new Error([msg, d.detail, d.stack].join('\n')) - jasmine.getEnv().currentSpec.fail(err) - }) - spyOn(console, 'error').andCallFake((msg, explanation = '') => { - const err = new Error(msg + explanation.toString()) - jasmine.getEnv().currentSpec.fail(err) - }) + spyOn(atom.notifications, "addFatalError").andCallFake((msg, d) => { + const err = new Error([msg, d.detail, d.stack].join("\n")); + jasmine.getEnv().currentSpec.fail(err); + }); + spyOn(console, "error").andCallFake((msg, explanation = "") => { + const err = new Error(msg + explanation.toString()); + jasmine.getEnv().currentSpec.fail(err); + }); - atom.configDirPath = Path.join(__dirname, 'fixtures') - }) + atom.configDirPath = Path.join(__dirname, "fixtures"); + }); - it('package is lazy-loaded', function () { - expect(atom.packages.isPackageLoaded('textual-velocity')).toBe(false) - expect(atom.packages.isPackageActive('textual-velocity')).toBe(false) - }) + it("package is lazy-loaded", function() { + expect(atom.packages.isPackageLoaded("textual-velocity")).toBe(false); + expect(atom.packages.isPackageActive("textual-velocity")).toBe(false); + }); - describe('when start-session command is triggered', function () { - beforeEach(function () { - const promise = atom.packages.activatePackage('textual-velocity') - this.workspaceElement.dispatchEvent(new CustomEvent('textual-velocity:start-session', {bubbles: true})) + describe("when start-session command is triggered", function() { + beforeEach(function() { + const promise = atom.packages.activatePackage("textual-velocity"); + this.workspaceElement.dispatchEvent( + new CustomEvent("textual-velocity:start-session", { bubbles: true }) + ); waitsForPromise(() => { - return promise - }) + return promise; + }); runs(() => { - this.panel = atom.workspace.getTopPanels().slice(-1)[0] - }) - }) + this.panel = atom.workspace.getTopPanels().slice(-1)[0]; + }); + }); - afterEach(function () { - atom.packages.deactivatePackage('textual-velocity') - this.panel = null - }) + afterEach(function() { + atom.packages.deactivatePackage("textual-velocity"); + this.panel = null; + }); - it('creates a top panel for the session', function () { - expect(this.panel.getItem().querySelector('.textual-velocity')).toEqual(jasmine.any(HTMLElement)) - }) + it("creates a top panel for the session", function() { + expect(this.panel.getItem().querySelector(".textual-velocity")).toEqual( + jasmine.any(HTMLElement) + ); + }); - it('should replaced start-session command with a stop-session command', function () { - const commands = atom.commands.getSnapshot() - expect(commands['textual-velocity:start-session']).toBeUndefined() - expect(commands['textual-velocity:stop-session']).toBeDefined() - }) + it("should replaced start-session command with a stop-session command", function() { + const commands = atom.commands.getSnapshot(); + expect(commands["textual-velocity:start-session"]).toBeUndefined(); + expect(commands["textual-velocity:stop-session"]).toBeDefined(); + }); - describe('when files are loaded', function () { - beforeEach(function () { + describe("when files are loaded", function() { + beforeEach(function() { waitsFor(() => { - return this.panel.getItem().innerHTML.match(' { - return promise - }) - }) + return promise; + }); + }); - it('should not render rows anymore', function () { - expect(atom.workspace.getTopPanels()).toEqual([]) - }) + it("should not render rows anymore", function() { + expect(atom.workspace.getTopPanels()).toEqual([]); + }); - it('should replaced stop-session command with a start-session command', function () { - const commands = atom.commands.getSnapshot() - expect(commands['textual-velocity:start-session']).toBeDefined() - expect(commands['textual-velocity:stop-session']).toBeUndefined() - }) - }) - }) - }) -}) + it("should replaced stop-session command with a start-session command", function() { + const commands = atom.commands.getSnapshot(); + expect(commands["textual-velocity:start-session"]).toBeDefined(); + expect(commands["textual-velocity:stop-session"]).toBeUndefined(); + }); + }); + }); + }); +}); diff --git a/spec/note-fields-spec.js b/spec/note-fields-spec.js index 8e47b14..4f6cc57 100644 --- a/spec/note-fields-spec.js +++ b/spec/note-fields-spec.js @@ -1,46 +1,49 @@ /* @flow */ -import NoteFields from '../lib/note-fields' +import NoteFields from "../lib/note-fields"; -describe('note-fields', () => { - let noteFields +describe("note-fields", () => { + let noteFields; - beforeEach(function () { - noteFields = new NoteFields() - }) + beforeEach(function() { + noteFields = new NoteFields(); + }); - describe('.forEach', function () { - let testNoteField + describe(".forEach", function() { + let testNoteField; - beforeEach(function () { + beforeEach(function() { testNoteField = { - notePropName: 'test', - value: (note, filename) => filename.split('.').slice(-1)[0] - } - noteFields.add(testNoteField) - noteFields.add({notePropName: 'content'}) // fields that only is there to indicate the existance of the field doesn't need a value transformer - }) - - it('should iterate each note field', function () { - const tmp = [] + notePropName: "test", + value: (note, filename) => filename.split(".").slice(-1)[0] + }; + noteFields.add(testNoteField); + noteFields.add({ notePropName: "content" }); // fields that only is there to indicate the existance of the field doesn't need a value transformer + }); + + it("should iterate each note field", function() { + const tmp = []; noteFields.forEach(noteField => { - tmp.push(noteField) - }) - expect(tmp[0]).toEqual(testNoteField) - }) - }) - - describe('.map', function () { - beforeEach(function () { + tmp.push(noteField); + }); + expect(tmp[0]).toEqual(testNoteField); + }); + }); + + describe(".map", function() { + beforeEach(function() { noteFields.add({ - notePropName: 'test', - value: (note, filename) => filename.split('.').slice(-1)[0] - }) - noteFields.add({notePropName: 'content'}) // fields that only is there to indicate the existance of the field doesn't need a value transformer - }) - - it('should return map values', function () { - expect(noteFields.map(noteField => noteField.notePropName)).toEqual(['test', 'content']) - }) - }) -}) + notePropName: "test", + value: (note, filename) => filename.split(".").slice(-1)[0] + }); + noteFields.add({ notePropName: "content" }); // fields that only is there to indicate the existance of the field doesn't need a value transformer + }); + + it("should return map values", function() { + expect(noteFields.map(noteField => noteField.notePropName)).toEqual([ + "test", + "content" + ]); + }); + }); +}); diff --git a/spec/notes-cache-spec.js b/spec/notes-cache-spec.js index 4d2f0ff..8a4223f 100644 --- a/spec/notes-cache-spec.js +++ b/spec/notes-cache-spec.js @@ -1,49 +1,49 @@ -'use babel' +"use babel"; -import {it, fit} from './_async-spec-helpers' // eslint-disable-line -import NotesCache from '../lib/notes-cache' +import { it, fit } from "./_async-spec-helpers"; // eslint-disable-line +import NotesCache from "../lib/notes-cache"; -describe('notes-cache', () => { - let notes, notesCache +describe("notes-cache", () => { + let notes, notesCache; - beforeEach(function () { - atom.enablePersistence = true - atom.stateStore.clear() - notesCache = new NotesCache('/notes') - }) + beforeEach(function() { + atom.enablePersistence = true; + atom.stateStore.clear(); + notesCache = new NotesCache("/notes"); + }); - afterEach(function () { - atom.enablePersistence = false - notesCache.dispose() - atom.stateStore.clear() - }) + afterEach(function() { + atom.enablePersistence = false; + notesCache.dispose(); + atom.stateStore.clear(); + }); - it('should save and load notes', async function () { - notes = await notesCache.load() - expect(notes).toEqual({}) + it("should save and load notes", async function() { + notes = await notesCache.load(); + expect(notes).toEqual({}); try { - await notesCache.save({'some-file': {foo: 'bar'}}) + await notesCache.save({ "some-file": { foo: "bar" } }); } catch (err) { - this.fail('save should work') + this.fail("save should work"); } - notes = await notesCache.load() - expect(notes).toEqual({'some-file': {foo: 'bar'}}) - }) + notes = await notesCache.load(); + expect(notes).toEqual({ "some-file": { foo: "bar" } }); + }); - it('should not save notes after clear-notes-cache command is called', async function () { - notes = await notesCache.load() + it("should not save notes after clear-notes-cache command is called", async function() { + notes = await notesCache.load(); - const workspaceView = atom.views.getView(atom.workspace) - jasmine.attachToDOM(workspaceView) - atom.commands.dispatch(workspaceView, 'textual-velocity:clear-notes-cache') + const workspaceView = atom.views.getView(atom.workspace); + jasmine.attachToDOM(workspaceView); + atom.commands.dispatch(workspaceView, "textual-velocity:clear-notes-cache"); try { - await notesCache.save({'some-file': {foo: 'bar'}}) + await notesCache.save({ "some-file": { foo: "bar" } }); } catch (err) { - this.fail('save should work') + this.fail("save should work"); } - notes = await notesCache.load() - expect(notes).toEqual({}) - }) -}) + notes = await notesCache.load(); + expect(notes).toEqual({}); + }); +}); diff --git a/spec/notes-file-filter-spec.js b/spec/notes-file-filter-spec.js index 901f9a6..d501a83 100644 --- a/spec/notes-file-filter-spec.js +++ b/spec/notes-file-filter-spec.js @@ -1,44 +1,76 @@ -'use babel' +"use babel"; -import defaultConfig from '../lib/default-config' -import Path from 'path' -import NotesFileFilter from '../lib/notes-file-filter' +import defaultConfig from "../lib/default-config"; +import Path from "path"; +import NotesFileFilter from "../lib/notes-file-filter"; -describe('notes-file-filter', () => { - let notesFileFilter +describe("notes-file-filter", () => { + let notesFileFilter; - beforeEach(function () { - atom.config.set('textual-velocity.ignoredNames', defaultConfig.ignoredNames.default) - atom.config.set('textual-velocity.excludeVcsIgnoredPaths', defaultConfig.excludeVcsIgnoredPaths.default) + beforeEach(function() { + atom.config.set( + "textual-velocity.ignoredNames", + defaultConfig.ignoredNames.default + ); + atom.config.set( + "textual-velocity.excludeVcsIgnoredPaths", + defaultConfig.excludeVcsIgnoredPaths.default + ); notesFileFilter = new NotesFileFilter(__dirname, { - exclusions: atom.config.get('textual-velocity.ignoredNames'), - excludeVcsIgnores: atom.config.get('textual-velocity.excludeVcsIgnoredPaths') - }) - }) + exclusions: atom.config.get("textual-velocity.ignoredNames"), + excludeVcsIgnores: atom.config.get( + "textual-velocity.excludeVcsIgnoredPaths" + ) + }); + }); - describe('.isAccepted', function () { - it('returns true for any text file', function () { - expect(notesFileFilter.isAccepted(Path.join(__dirname, 'file.txt'))).toBe(true) - expect(notesFileFilter.isAccepted(Path.join(__dirname, 'file.md'))).toBe(true) - expect(notesFileFilter.isAccepted(Path.join(__dirname, 'file.js'))).toBe(true) - expect(notesFileFilter.isAccepted(Path.join(__dirname, 'file.json'))).toBe(true) - expect(notesFileFilter.isAccepted(Path.join(__dirname, 'file.bash'))).toBe(true) - }) + describe(".isAccepted", function() { + it("returns true for any text file", function() { + expect(notesFileFilter.isAccepted(Path.join(__dirname, "file.txt"))).toBe( + true + ); + expect(notesFileFilter.isAccepted(Path.join(__dirname, "file.md"))).toBe( + true + ); + expect(notesFileFilter.isAccepted(Path.join(__dirname, "file.js"))).toBe( + true + ); + expect( + notesFileFilter.isAccepted(Path.join(__dirname, "file.json")) + ).toBe(true); + expect( + notesFileFilter.isAccepted(Path.join(__dirname, "file.bash")) + ).toBe(true); + }); - it('returns false for any non-text file', function () { - expect(notesFileFilter.isAccepted(Path.join(__dirname, 'file.exe'))).toBe(false) - expect(notesFileFilter.isAccepted(Path.join(__dirname, 'file.jpg'))).toBe(false) - expect(notesFileFilter.isAccepted(Path.join(__dirname, 'file.zip'))).toBe(false) - expect(notesFileFilter.isAccepted(Path.join(__dirname, 'file.pdf'))).toBe(false) - }) + it("returns false for any non-text file", function() { + expect(notesFileFilter.isAccepted(Path.join(__dirname, "file.exe"))).toBe( + false + ); + expect(notesFileFilter.isAccepted(Path.join(__dirname, "file.jpg"))).toBe( + false + ); + expect(notesFileFilter.isAccepted(Path.join(__dirname, "file.zip"))).toBe( + false + ); + expect(notesFileFilter.isAccepted(Path.join(__dirname, "file.pdf"))).toBe( + false + ); + }); - it('returns false for any excluded file', function () { - expect(notesFileFilter.isAccepted(Path.join(__dirname, '.git/index'))).toBe(false) - expect(notesFileFilter.isAccepted(Path.join(__dirname, '.DS_Store'))).toBe(false) - }) + it("returns false for any excluded file", function() { + expect( + notesFileFilter.isAccepted(Path.join(__dirname, ".git/index")) + ).toBe(false); + expect( + notesFileFilter.isAccepted(Path.join(__dirname, ".DS_Store")) + ).toBe(false); + }); - it('returns false for nv/nvalt settings file', function () { - expect(notesFileFilter.isAccepted(Path.join(__dirname, 'Notes & Settings'))).toBe(false) - }) - }) -}) + it("returns false for nv/nvalt settings file", function() { + expect( + notesFileFilter.isAccepted(Path.join(__dirname, "Notes & Settings")) + ).toBe(false); + }); + }); +}); diff --git a/spec/preview-element-spec.js b/spec/preview-element-spec.js index 5c378dd..7dbbc0f 100644 --- a/spec/preview-element-spec.js +++ b/spec/preview-element-spec.js @@ -1,46 +1,46 @@ /* @flow */ -import PreviewElement from '../lib/preview-element' - -describe('preview-element', function () { - let preview - - beforeEach(function () { - preview = new PreviewElement() - }) - - afterEach(function () { - preview.dispose() - }) - - it('should have a title compatible for tab panes', function () { - expect(preview.getTitle()).toEqual(jasmine.any(String)) - expect(preview.getLongTitle()).toEqual(jasmine.any(String)) - }) - - describe('when preview is updated', function () { - let html - - beforeEach(function () { - preview.updatePreview('/test/path.txt', 'foo\nbar\nbaz', /ba/) - html = preview.innerHTML - }) - - it('should render content', function () { - expect(html).toContain('foo') - }) - - it('should highlight matches', function () { - expect(html).toMatch(/ba<\/span>r/) - expect(html).toMatch(/ba<\/span>z/) - }) - - it('should render new lines', function () { - expect(html).toContain('
') - }) - - it('should have a path', function () { - expect(preview.getPath()).toEqual('/test/path.txt') - }) - }) -}) +import PreviewElement from "../lib/preview-element"; + +describe("preview-element", function() { + let preview; + + beforeEach(function() { + preview = new PreviewElement(); + }); + + afterEach(function() { + preview.dispose(); + }); + + it("should have a title compatible for tab panes", function() { + expect(preview.getTitle()).toEqual(jasmine.any(String)); + expect(preview.getLongTitle()).toEqual(jasmine.any(String)); + }); + + describe("when preview is updated", function() { + let html; + + beforeEach(function() { + preview.updatePreview("/test/path.txt", "foo\nbar\nbaz", /ba/); + html = preview.innerHTML; + }); + + it("should render content", function() { + expect(html).toContain("foo"); + }); + + it("should highlight matches", function() { + expect(html).toMatch(/ba<\/span>r/); + expect(html).toMatch(/ba<\/span>z/); + }); + + it("should render new lines", function() { + expect(html).toContain("
"); + }); + + it("should have a path", function() { + expect(preview.getPath()).toEqual("/test/path.txt"); + }); + }); +}); diff --git a/spec/react/edit-cell-str-spec.js b/spec/react/edit-cell-str-spec.js index 9100ab8..c68c43f 100644 --- a/spec/react/edit-cell-str-spec.js +++ b/spec/react/edit-cell-str-spec.js @@ -1,74 +1,76 @@ /* @flow */ -import React from 'react' -import ReactTestUtils from 'react-dom/test-utils' -import EditCellStr from '../../lib/react/edit-cell-str' -import dispatchKeyDownEvent from '../dispatch-keydown-event' +import React from "react"; +import ReactTestUtils from "react-dom/test-utils"; +import EditCellStr from "../../lib/react/edit-cell-str"; +import dispatchKeyDownEvent from "../dispatch-keydown-event"; -describe('react/edit-cell-str', function () { - let saveSpy, abortSpy - let component, input +describe("react/edit-cell-str", function() { + let saveSpy, abortSpy; + let component, input; - beforeEach(function () { - saveSpy = jasmine.createSpy('save') - abortSpy = jasmine.createSpy('abort') - component = ReactTestUtils.renderIntoDocument() - input = component.input - }) + beforeEach(function() { + saveSpy = jasmine.createSpy("save"); + abortSpy = jasmine.createSpy("abort"); + component = ReactTestUtils.renderIntoDocument( + + ); + input = component.input; + }); - it('renders an input with the initial value', function () { - expect(component.input.type).toEqual('text') - expect(component.input.value).toEqual('foo') - }) + it("renders an input with the initial value", function() { + expect(component.input.type).toEqual("text"); + expect(component.input.value).toEqual("foo"); + }); - describe('when ', function () { - beforeEach(function () { - dispatchKeyDownEvent(input, { keyCode: 13 }) - }) + describe("when ", function() { + beforeEach(function() { + dispatchKeyDownEvent(input, { keyCode: 13 }); + }); - it('aborts since value has not changed', function () { - expect(abortSpy).toHaveBeenCalled() - expect(saveSpy).not.toHaveBeenCalled() - }) - }) + it("aborts since value has not changed", function() { + expect(abortSpy).toHaveBeenCalled(); + expect(saveSpy).not.toHaveBeenCalled(); + }); + }); - describe('when input is changed', function () { - beforeEach(function () { - component.input.value = ' a b c ' - ReactTestUtils.Simulate.change(input) - dispatchKeyDownEvent(input, { keyCode: 40 }) // - }) + describe("when input is changed", function() { + beforeEach(function() { + component.input.value = " a b c "; + ReactTestUtils.Simulate.change(input); + dispatchKeyDownEvent(input, { keyCode: 40 }); // + }); - it('gets the changed value', function () { - expect(component.input.value).toEqual(' a b c ') - }) + it("gets the changed value", function() { + expect(component.input.value).toEqual(" a b c "); + }); - it('does not call save or abort', function () { - expect(saveSpy).not.toHaveBeenCalled() - expect(abortSpy).not.toHaveBeenCalled() - }) + it("does not call save or abort", function() { + expect(saveSpy).not.toHaveBeenCalled(); + expect(abortSpy).not.toHaveBeenCalled(); + }); - describe('when ', function () { - beforeEach(function () { - dispatchKeyDownEvent(input, { keyCode: 13 }) - }) + describe("when ", function() { + beforeEach(function() { + dispatchKeyDownEvent(input, { keyCode: 13 }); + }); - it('saves the changed value', function () { - expect(saveSpy).toHaveBeenCalled() - expect(saveSpy).toHaveBeenCalledWith('a b c') - expect(abortSpy).not.toHaveBeenCalled() - }) - }) + it("saves the changed value", function() { + expect(saveSpy).toHaveBeenCalled(); + expect(saveSpy).toHaveBeenCalledWith("a b c"); + expect(abortSpy).not.toHaveBeenCalled(); + }); + }); - describe('when ', function () { - beforeEach(function () { - dispatchKeyDownEvent(input, { keyCode: 27 }) - }) + describe("when ", function() { + beforeEach(function() { + dispatchKeyDownEvent(input, { keyCode: 27 }); + }); - it('aborts', function () { - expect(abortSpy).toHaveBeenCalled() - expect(saveSpy).not.toHaveBeenCalled() - }) - }) - }) -}) + it("aborts", function() { + expect(abortSpy).toHaveBeenCalled(); + expect(saveSpy).not.toHaveBeenCalled(); + }); + }); + }); +}); diff --git a/spec/reducers/column-headers-spec.js b/spec/reducers/column-headers-spec.js index 87f025e..b71ad25 100644 --- a/spec/reducers/column-headers-spec.js +++ b/spec/reducers/column-headers-spec.js @@ -1,39 +1,42 @@ /* @flow */ -import * as A from '../../lib/action-creators' -import Columns from '../../lib/columns' -import FileIconColumn from '../../lib/columns/file-icon-column' -import SummaryColumn from '../../lib/columns/summary-column' -import makeColumnHeadersReducer from '../../lib/reducers/column-headers' - -describe('reducers/column-headers', () => { - let state - let columnHeadersReducer - - beforeEach(function () { - const columns = new Columns() - columns.add(new SummaryColumn({sortField: 'name', editCellName: ''})) - columns.add(new FileIconColumn({sortField: 'ext'})) - - columnHeadersReducer = makeColumnHeadersReducer(columns) - }) - - it('should return defaults when state is missing', function () { - state = columnHeadersReducer(state, A.initialScanDone()) - expect(state).toEqual(jasmine.any(Array)) - expect(state.length).toEqual(2) - - expect(state[0].sortField).toEqual(jasmine.any(String)) - expect(state[0].title).toEqual(jasmine.any(String)) - expect(state[0].width).toEqual(jasmine.any(Number)) - }) - - it('should exclude hidden columns', function () { - state = columnHeadersReducer(state, A.changeHiddenColumns(['summary'])) - expect(state).toEqual(jasmine.any(Array)) - expect(state.length).toEqual(1) - - state = columnHeadersReducer(state, A.changeHiddenColumns(['summary', 'file-type'])) - expect(state).toEqual([]) - }) -}) +import * as A from "../../lib/action-creators"; +import Columns from "../../lib/columns"; +import FileIconColumn from "../../lib/columns/file-icon-column"; +import SummaryColumn from "../../lib/columns/summary-column"; +import makeColumnHeadersReducer from "../../lib/reducers/column-headers"; + +describe("reducers/column-headers", () => { + let state; + let columnHeadersReducer; + + beforeEach(function() { + const columns = new Columns(); + columns.add(new SummaryColumn({ sortField: "name", editCellName: "" })); + columns.add(new FileIconColumn({ sortField: "ext" })); + + columnHeadersReducer = makeColumnHeadersReducer(columns); + }); + + it("should return defaults when state is missing", function() { + state = columnHeadersReducer(state, A.initialScanDone()); + expect(state).toEqual(jasmine.any(Array)); + expect(state.length).toEqual(2); + + expect(state[0].sortField).toEqual(jasmine.any(String)); + expect(state[0].title).toEqual(jasmine.any(String)); + expect(state[0].width).toEqual(jasmine.any(Number)); + }); + + it("should exclude hidden columns", function() { + state = columnHeadersReducer(state, A.changeHiddenColumns(["summary"])); + expect(state).toEqual(jasmine.any(Array)); + expect(state.length).toEqual(1); + + state = columnHeadersReducer( + state, + A.changeHiddenColumns(["summary", "file-type"]) + ); + expect(state).toEqual([]); + }); +}); diff --git a/spec/reducers/initial-scan-spec.js b/spec/reducers/initial-scan-spec.js index ac4f3c1..a2c8e90 100644 --- a/spec/reducers/initial-scan-spec.js +++ b/spec/reducers/initial-scan-spec.js @@ -1,62 +1,68 @@ /* @flow */ -import * as A from '../../lib/action-creators' -import initialScanReducer from '../../lib/reducers/initial-scan' - -describe('reducers/initial-scan', () => { - let state: InitialScan - - beforeEach(function () { - state = initialScanReducer(undefined, A.startInitialScan()) - }) - - describe('when file-added action', function () { - beforeEach(function () { - state = initialScanReducer(state, A.fileAdded({ - filename: 'a', - stats: {mtime: new Date()} - })) - state = initialScanReducer(state, A.fileAdded({ - filename: 'b', - stats: {mtime: new Date()} - })) - }) - - it('should append new files', function () { - expect(state.rawFiles.length).toEqual(2) - expect(state.rawFiles[0].filename).toEqual('a') - }) - - it('should not be done yet', function () { - expect(state.done).toBe(false) - }) - }) - - describe('when initial-scan-done action', function () { - let prevState - - beforeEach(function () { - prevState = state - state = initialScanReducer(state, A.initialScanDone()) - }) - - it('should set done flag to true', function () { - expect(state.done).toBe(true) - }) - - it('should still have raw files', function () { - expect(state.rawFiles).toBe(prevState.rawFiles) - }) - - describe('when initial scan raw files are read ', function () { - beforeEach(function () { - state = initialScanReducer(state, A.initialScanRawFilesRead()) - expect(state.done).toBe(true) - }) - - it('should no longer have any raw files', function () { - expect(state.rawFiles).toEqual([]) - }) - }) - }) -}) +import * as A from "../../lib/action-creators"; +import initialScanReducer from "../../lib/reducers/initial-scan"; + +describe("reducers/initial-scan", () => { + let state: InitialScan; + + beforeEach(function() { + state = initialScanReducer(undefined, A.startInitialScan()); + }); + + describe("when file-added action", function() { + beforeEach(function() { + state = initialScanReducer( + state, + A.fileAdded({ + filename: "a", + stats: { mtime: new Date() } + }) + ); + state = initialScanReducer( + state, + A.fileAdded({ + filename: "b", + stats: { mtime: new Date() } + }) + ); + }); + + it("should append new files", function() { + expect(state.rawFiles.length).toEqual(2); + expect(state.rawFiles[0].filename).toEqual("a"); + }); + + it("should not be done yet", function() { + expect(state.done).toBe(false); + }); + }); + + describe("when initial-scan-done action", function() { + let prevState; + + beforeEach(function() { + prevState = state; + state = initialScanReducer(state, A.initialScanDone()); + }); + + it("should set done flag to true", function() { + expect(state.done).toBe(true); + }); + + it("should still have raw files", function() { + expect(state.rawFiles).toBe(prevState.rawFiles); + }); + + describe("when initial scan raw files are read ", function() { + beforeEach(function() { + state = initialScanReducer(state, A.initialScanRawFilesRead()); + expect(state.done).toBe(true); + }); + + it("should no longer have any raw files", function() { + expect(state.rawFiles).toEqual([]); + }); + }); + }); +}); diff --git a/spec/reducers/list-height-spec.js b/spec/reducers/list-height-spec.js index 2f1c0f7..0037f32 100644 --- a/spec/reducers/list-height-spec.js +++ b/spec/reducers/list-height-spec.js @@ -1,50 +1,50 @@ /* @flow */ -import * as A from '../../lib/action-creators' -import listHeightReducer from '../../lib/reducers/list-height' - -describe('reducers/list-height', () => { - let state - - beforeEach(function () { - state = listHeightReducer(undefined, A.initialScanDone()) - }) - - it('should have a default height', function () { - expect(state).toEqual(jasmine.any(Number)) - }) - - it('should return state unless defaults are missing', function () { - const prevState = state - state = listHeightReducer(state, A.initialScanDone()) - expect(state).toBe(prevState) - }) - - describe('when resized list', function () { - beforeEach(function () { - state = listHeightReducer(state, A.resizeList(123)) - }) - - it('should only update list height', function () { - expect(state).toEqual(123) - }) - }) - - describe('when changed list height', function () { - beforeEach(function () { - state = listHeightReducer(state, A.changeListHeight(123)) - }) - - it('should only update list height', function () { - expect(state).toEqual(123) - }) - }) - - describe('when other random action', function () { - it('should update list height', function () { - const prevState = state - state = listHeightReducer(state, A.initialScanDone()) - expect(state).toBe(prevState) - }) - }) -}) +import * as A from "../../lib/action-creators"; +import listHeightReducer from "../../lib/reducers/list-height"; + +describe("reducers/list-height", () => { + let state; + + beforeEach(function() { + state = listHeightReducer(undefined, A.initialScanDone()); + }); + + it("should have a default height", function() { + expect(state).toEqual(jasmine.any(Number)); + }); + + it("should return state unless defaults are missing", function() { + const prevState = state; + state = listHeightReducer(state, A.initialScanDone()); + expect(state).toBe(prevState); + }); + + describe("when resized list", function() { + beforeEach(function() { + state = listHeightReducer(state, A.resizeList(123)); + }); + + it("should only update list height", function() { + expect(state).toEqual(123); + }); + }); + + describe("when changed list height", function() { + beforeEach(function() { + state = listHeightReducer(state, A.changeListHeight(123)); + }); + + it("should only update list height", function() { + expect(state).toEqual(123); + }); + }); + + describe("when other random action", function() { + it("should update list height", function() { + const prevState = state; + state = listHeightReducer(state, A.initialScanDone()); + expect(state).toBe(prevState); + }); + }); +}); diff --git a/spec/reducers/notes-spec.js b/spec/reducers/notes-spec.js index e7d474a..145742c 100644 --- a/spec/reducers/notes-spec.js +++ b/spec/reducers/notes-spec.js @@ -1,173 +1,180 @@ /* @flow */ -import * as A from '../../lib/action-creators' -import FileReaders from '../../lib/file-readers' -import NoteFields from '../../lib/note-fields' -import makeNotesReducer from '../../lib/reducers/notes' - -describe('reducers/notes', () => { - let state: Notes - let nextInitialScan: InitialScan - let notesReducer - - beforeEach(function () { - const fileReaders = new FileReaders() +import * as A from "../../lib/action-creators"; +import FileReaders from "../../lib/file-readers"; +import NoteFields from "../../lib/note-fields"; +import makeNotesReducer from "../../lib/reducers/notes"; + +describe("reducers/notes", () => { + let state: Notes; + let nextInitialScan: InitialScan; + let notesReducer; + + beforeEach(function() { + const fileReaders = new FileReaders(); fileReaders.add({ - notePropName: 'content', - read: (path: string, fileStats: FsStats, callback: NodeCallback) => callback(null, `content for ${path}`) - }) + notePropName: "content", + read: (path: string, fileStats: FsStats, callback: NodeCallback) => + callback(null, `content for ${path}`) + }); - const noteFields = new NoteFields() + const noteFields = new NoteFields(); noteFields.add({ - notePropName: 'ext', - value: (note, filename) => filename.split('.').slice(-1)[0] - }) + notePropName: "ext", + value: (note, filename) => filename.split(".").slice(-1)[0] + }); // Some fields are set by a file-reader, in those cases the field is only there to indicate that the field exist - noteFields.add({notePropName: 'content'}) + noteFields.add({ notePropName: "content" }); nextInitialScan = { done: false, rawFiles: [] - } + }; - notesReducer = makeNotesReducer(fileReaders, noteFields) - state = notesReducer(undefined, A.startInitialScan(), nextInitialScan) - }) + notesReducer = makeNotesReducer(fileReaders, noteFields); + state = notesReducer(undefined, A.startInitialScan(), nextInitialScan); + }); - it('should have an empty object', function () { - expect(state).toEqual({}) - }) + it("should have an empty object", function() { + expect(state).toEqual({}); + }); - describe('when initial-scan-done action', function () { - beforeEach(function () { + describe("when initial-scan-done action", function() { + beforeEach(function() { nextInitialScan = { done: true, - rawFiles: [{ - filename: 'a.txt', - stats: {mtime: new Date()} - }, { - filename: 'b.md', - stats: {mtime: new Date()} - }] - } - state = notesReducer(state, A.initialScanDone(), nextInitialScan) - }) - - it('should reduce notes from raw notes', function () { - expect(Object.keys(state).length).toEqual(2) - }) - - it('should apply noteFields on notes', function () { - expect(state['a.txt']).toEqual(jasmine.any(Object)) - expect(state['a.txt'].ext).toEqual('txt') - expect(state['b.md'].ext).toEqual('md') - }) - - it('should set an unique id on the note', function () { - expect(state['a.txt'].id).toEqual(jasmine.any(String)) - }) - - it('should set stats object on note', function () { - expect(state['a.txt'].stats).toEqual(jasmine.any(Object)) - }) - }) - - describe('when file is added', function () { - let action - - beforeEach(function () { + rawFiles: [ + { + filename: "a.txt", + stats: { mtime: new Date() } + }, + { + filename: "b.md", + stats: { mtime: new Date() } + } + ] + }; + state = notesReducer(state, A.initialScanDone(), nextInitialScan); + }); + + it("should reduce notes from raw notes", function() { + expect(Object.keys(state).length).toEqual(2); + }); + + it("should apply noteFields on notes", function() { + expect(state["a.txt"]).toEqual(jasmine.any(Object)); + expect(state["a.txt"].ext).toEqual("txt"); + expect(state["b.md"].ext).toEqual("md"); + }); + + it("should set an unique id on the note", function() { + expect(state["a.txt"].id).toEqual(jasmine.any(String)); + }); + + it("should set stats object on note", function() { + expect(state["a.txt"].stats).toEqual(jasmine.any(Object)); + }); + }); + + describe("when file is added", function() { + let action; + + beforeEach(function() { nextInitialScan = { done: false, - rawFiles: [{ - filename: 'a.txt', - stats: {mtime: new Date()} - }, { - filename: 'b.md', - stats: {mtime: new Date()} - }] - } + rawFiles: [ + { + filename: "a.txt", + stats: { mtime: new Date() } + }, + { + filename: "b.md", + stats: { mtime: new Date() } + } + ] + }; action = A.fileAdded({ - filename: 'cesar.txt', - stats: {mtime: new Date()} - }) - }) - - describe('when initial scan is not yet done', function () { - beforeEach(function () { - state = notesReducer(state, action, nextInitialScan) - }) - - it('should not do anything', function () { - expect(state).toEqual({}) - }) - }) - - describe('when initial scan is done', function () { - let prevState - - beforeEach(function () { - nextInitialScan.done = true + filename: "cesar.txt", + stats: { mtime: new Date() } + }); + }); + + describe("when initial scan is not yet done", function() { + beforeEach(function() { + state = notesReducer(state, action, nextInitialScan); + }); + + it("should not do anything", function() { + expect(state).toEqual({}); + }); + }); + + describe("when initial scan is done", function() { + let prevState; + + beforeEach(function() { + nextInitialScan.done = true; prevState = { - 'alice.txt': { - id: '1', - stats: {mtime: new Date()}, + "alice.txt": { + id: "1", + stats: { mtime: new Date() }, ready: false, - ext: 'txt', - name: 'alice' + ext: "txt", + name: "alice" }, - 'bob.md': { - id: '2', - stats: {mtime: new Date()}, + "bob.md": { + id: "2", + stats: { mtime: new Date() }, ready: false, - ext: 'md', - name: 'bob' + ext: "md", + name: "bob" } - } - state = notesReducer(prevState, action, nextInitialScan) - }) + }; + state = notesReducer(prevState, action, nextInitialScan); + }); - it('should add new notes', function () { - expect(state['cesar.txt']).toEqual({ + it("should add new notes", function() { + expect(state["cesar.txt"]).toEqual({ id: jasmine.any(String), - stats: {mtime: jasmine.any(Date)}, + stats: { mtime: jasmine.any(Date) }, ready: false, - ext: 'txt', - name: '' - }) - }) - - describe('when file is removed', function () { - beforeEach(function () { - action = A.fileDeleted('cesar.txt') - state = notesReducer(state, action, nextInitialScan) - }) - - it('should remove corresponding note', function () { - expect(state).toEqual(prevState) - }) - }) - - describe('when a file is read', function () { - beforeEach(function () { + ext: "txt", + name: "" + }); + }); + + describe("when file is removed", function() { + beforeEach(function() { + action = A.fileDeleted("cesar.txt"); + state = notesReducer(state, action, nextInitialScan); + }); + + it("should remove corresponding note", function() { + expect(state).toEqual(prevState); + }); + }); + + describe("when a file is read", function() { + beforeEach(function() { action = A.fileRead({ - filename: 'bob.md', - notePropName: 'content', - value: 'content for bob.md' - }) - state = notesReducer(prevState, action, nextInitialScan) - }) - - it('should add field to intended note', function () { - expect(state['bob.md'].content).toEqual('content for bob.md') - expect(state['alice.txt'].content).toEqual() - }) - - it('should set note with all fields as ready', function () { - expect(state['bob.md'].ready).toBe(true) - expect(state['alice.txt'].ready).toBe(false) - }) - }) - }) - }) -}) + filename: "bob.md", + notePropName: "content", + value: "content for bob.md" + }); + state = notesReducer(prevState, action, nextInitialScan); + }); + + it("should add field to intended note", function() { + expect(state["bob.md"].content).toEqual("content for bob.md"); + expect(state["alice.txt"].content).toEqual(); + }); + + it("should set note with all fields as ready", function() { + expect(state["bob.md"].ready).toBe(true); + expect(state["alice.txt"].ready).toBe(false); + }); + }); + }); + }); +}); diff --git a/spec/reducers/row-height-spec.js b/spec/reducers/row-height-spec.js index 56bd8d5..8950857 100644 --- a/spec/reducers/row-height-spec.js +++ b/spec/reducers/row-height-spec.js @@ -1,40 +1,40 @@ /* @flow */ -import * as A from '../../lib/action-creators' -import rowHeightReducer from '../../lib/reducers/row-height' - -describe('reducers/row-height', () => { - let state - - beforeEach(function () { - state = rowHeightReducer(undefined, A.initialScanDone()) - }) - - it('should return default', function () { - expect(state).toEqual(jasmine.any(Number)) - }) - - it('should return state unless defaults are missing', function () { - const prevState = state - state = rowHeightReducer(state, A.initialScanDone()) - expect(state).toBe(prevState) - }) - - describe('when change row height', function () { - beforeEach(function () { - state = rowHeightReducer(state, A.changeRowHeight(20)) - }) - - it('should only update row height', function () { - expect(state).toEqual(20) - }) - }) - - describe('when other random action', function () { - it('should update list height', function () { - const prevState = state - state = rowHeightReducer(state, A.initialScanDone()) - expect(state).toBe(prevState) - }) - }) -}) +import * as A from "../../lib/action-creators"; +import rowHeightReducer from "../../lib/reducers/row-height"; + +describe("reducers/row-height", () => { + let state; + + beforeEach(function() { + state = rowHeightReducer(undefined, A.initialScanDone()); + }); + + it("should return default", function() { + expect(state).toEqual(jasmine.any(Number)); + }); + + it("should return state unless defaults are missing", function() { + const prevState = state; + state = rowHeightReducer(state, A.initialScanDone()); + expect(state).toBe(prevState); + }); + + describe("when change row height", function() { + beforeEach(function() { + state = rowHeightReducer(state, A.changeRowHeight(20)); + }); + + it("should only update row height", function() { + expect(state).toEqual(20); + }); + }); + + describe("when other random action", function() { + it("should update list height", function() { + const prevState = state; + state = rowHeightReducer(state, A.initialScanDone()); + expect(state).toBe(prevState); + }); + }); +}); diff --git a/spec/reducers/scroll-top-spec.js b/spec/reducers/scroll-top-spec.js index 705e0a7..1d37e58 100644 --- a/spec/reducers/scroll-top-spec.js +++ b/spec/reducers/scroll-top-spec.js @@ -1,138 +1,192 @@ /* @flow */ -import * as A from '../../lib/action-creators' -import scrollTopReducer from '../../lib/reducers/scroll-top' - -describe('reducers/scroll-top', () => { - let state: any - let nextListHeight: number - let nextRowHeight: number - let nextSelectedNote: ?SelectedNote - - beforeEach(function () { - state = undefined - nextListHeight = 100 - nextRowHeight = 20 - nextSelectedNote = null - }) - - describe('when search', function () { - beforeEach(function () { - state = scrollTopReducer(state, A.search('abc'), nextListHeight, nextRowHeight, nextSelectedNote) - }) - - it('should force scrollTop to top', function () { - expect(state).toEqual(0) - }) - }) - - describe('when reset search', function () { - beforeEach(function () { - state = scrollTopReducer(state, A.resetSearch(), nextListHeight, nextRowHeight, nextSelectedNote) - }) - - it('should force scrollTop to top', function () { - expect(state).toEqual(0) - }) - }) - - describe('when select next', function () { - sharedAdjustScrollTopSpecs(A.selectNext()) - }) - - describe('when select prev', function () { - sharedAdjustScrollTopSpecs(A.selectPrev()) - }) - - describe('when active path changes', function () { - beforeEach(function () { - nextSelectedNote = {index: 5, filename: 'alice.txt'} - }) - - sharedAdjustScrollTopSpecs(A.changedActivePaneItem('alice.txt')) - }) - - describe('when any other action', function () { - beforeEach(function () { - state = 123 - state = scrollTopReducer(state, A.startInitialScan(), nextListHeight, nextRowHeight, nextSelectedNote) - }) - - it('should return current scrollTop', function () { - expect(state).toEqual(123) - }) - }) - - function sharedAdjustScrollTopSpecs (action: Action) { - describe('when there is no selection', function () { - beforeEach(function () { - nextSelectedNote = null - state = 5 - state = scrollTopReducer(state, action, nextListHeight, nextRowHeight, nextSelectedNote) - }) - - it('should return current scrollTop', function () { - expect(state).toEqual(5) - }) - }) - - describe('when selected item is within the viewport', function () { - beforeEach(function () { - nextSelectedNote = {index: 5, filename: 'alice.txt'} - state = 25 - state = scrollTopReducer(state, action, nextListHeight, nextRowHeight, nextSelectedNote) - }) - - it('should return current scrollTop', function () { - expect(state).toEqual(25) - }) - }) - - describe('when selected item is before the viewport', function () { - beforeEach(function () { - nextSelectedNote = {index: 1, filename: 'alice.txt'} - state = 25 - state = scrollTopReducer(state, action, nextListHeight, nextRowHeight, nextSelectedNote) - }) - - it('should force scrollTop to have the selected item in view at top', function () { - expect(state).toEqual(20) - }) - }) - - describe('when selected item is after the viewport', function () { - beforeEach(function () { - nextSelectedNote = {index: 20, filename: 'alice.txt'} - state = scrollTopReducer(state, action, nextListHeight, nextRowHeight, nextSelectedNote) - }) - - it('should force scrollTop to have the selected item at the bottom of the viewport', function () { - expect(state).toEqual(320) // 400 - 80 (the height of the other items before the selected) - }) - }) - - describe('when selected item is only half visible at the end of the viewport', function () { - beforeEach(function () { - state = 5 - nextSelectedNote = {index: 5, filename: 'alice.txt'} - state = scrollTopReducer(state, action, nextListHeight, nextRowHeight, nextSelectedNote) - }) - - it('should force scrollTop to have the selected item at the bottom of the viewport', function () { - expect(state).toEqual(20) - }) - }) - - describe('when selected item is only half visible at the top of the viewport', function () { - beforeEach(function () { - state = 25 - nextSelectedNote = {index: 1, filename: 'alice.txt'} - state = scrollTopReducer(state, action, nextListHeight, nextRowHeight, nextSelectedNote) - }) - - it('should force scrollTop to have the selected item at the top of the viewport', function () { - expect(state).toEqual(20) - }) - }) +import * as A from "../../lib/action-creators"; +import scrollTopReducer from "../../lib/reducers/scroll-top"; + +describe("reducers/scroll-top", () => { + let state: any; + let nextListHeight: number; + let nextRowHeight: number; + let nextSelectedNote: ?SelectedNote; + + beforeEach(function() { + state = undefined; + nextListHeight = 100; + nextRowHeight = 20; + nextSelectedNote = null; + }); + + describe("when search", function() { + beforeEach(function() { + state = scrollTopReducer( + state, + A.search("abc"), + nextListHeight, + nextRowHeight, + nextSelectedNote + ); + }); + + it("should force scrollTop to top", function() { + expect(state).toEqual(0); + }); + }); + + describe("when reset search", function() { + beforeEach(function() { + state = scrollTopReducer( + state, + A.resetSearch(), + nextListHeight, + nextRowHeight, + nextSelectedNote + ); + }); + + it("should force scrollTop to top", function() { + expect(state).toEqual(0); + }); + }); + + describe("when select next", function() { + sharedAdjustScrollTopSpecs(A.selectNext()); + }); + + describe("when select prev", function() { + sharedAdjustScrollTopSpecs(A.selectPrev()); + }); + + describe("when active path changes", function() { + beforeEach(function() { + nextSelectedNote = { index: 5, filename: "alice.txt" }; + }); + + sharedAdjustScrollTopSpecs(A.changedActivePaneItem("alice.txt")); + }); + + describe("when any other action", function() { + beforeEach(function() { + state = 123; + state = scrollTopReducer( + state, + A.startInitialScan(), + nextListHeight, + nextRowHeight, + nextSelectedNote + ); + }); + + it("should return current scrollTop", function() { + expect(state).toEqual(123); + }); + }); + + function sharedAdjustScrollTopSpecs(action: Action) { + describe("when there is no selection", function() { + beforeEach(function() { + nextSelectedNote = null; + state = 5; + state = scrollTopReducer( + state, + action, + nextListHeight, + nextRowHeight, + nextSelectedNote + ); + }); + + it("should return current scrollTop", function() { + expect(state).toEqual(5); + }); + }); + + describe("when selected item is within the viewport", function() { + beforeEach(function() { + nextSelectedNote = { index: 5, filename: "alice.txt" }; + state = 25; + state = scrollTopReducer( + state, + action, + nextListHeight, + nextRowHeight, + nextSelectedNote + ); + }); + + it("should return current scrollTop", function() { + expect(state).toEqual(25); + }); + }); + + describe("when selected item is before the viewport", function() { + beforeEach(function() { + nextSelectedNote = { index: 1, filename: "alice.txt" }; + state = 25; + state = scrollTopReducer( + state, + action, + nextListHeight, + nextRowHeight, + nextSelectedNote + ); + }); + + it("should force scrollTop to have the selected item in view at top", function() { + expect(state).toEqual(20); + }); + }); + + describe("when selected item is after the viewport", function() { + beforeEach(function() { + nextSelectedNote = { index: 20, filename: "alice.txt" }; + state = scrollTopReducer( + state, + action, + nextListHeight, + nextRowHeight, + nextSelectedNote + ); + }); + + it("should force scrollTop to have the selected item at the bottom of the viewport", function() { + expect(state).toEqual(320); // 400 - 80 (the height of the other items before the selected) + }); + }); + + describe("when selected item is only half visible at the end of the viewport", function() { + beforeEach(function() { + state = 5; + nextSelectedNote = { index: 5, filename: "alice.txt" }; + state = scrollTopReducer( + state, + action, + nextListHeight, + nextRowHeight, + nextSelectedNote + ); + }); + + it("should force scrollTop to have the selected item at the bottom of the viewport", function() { + expect(state).toEqual(20); + }); + }); + + describe("when selected item is only half visible at the top of the viewport", function() { + beforeEach(function() { + state = 25; + nextSelectedNote = { index: 1, filename: "alice.txt" }; + state = scrollTopReducer( + state, + action, + nextListHeight, + nextRowHeight, + nextSelectedNote + ); + }); + + it("should force scrollTop to have the selected item at the top of the viewport", function() { + expect(state).toEqual(20); + }); + }); } -}) +}); diff --git a/spec/reducers/selected-note-spec.js b/spec/reducers/selected-note-spec.js index f8c309e..a614363 100644 --- a/spec/reducers/selected-note-spec.js +++ b/spec/reducers/selected-note-spec.js @@ -1,286 +1,334 @@ /* @flow */ -import * as actions from '../../lib/action-creators' -import selectedNoteReducer from '../../lib/reducers/selected-note' - -describe('reducers/selected-note', () => { - let action: Action - let dir: string - let nextSifterResult: SifterResult - let selectedNote: SelectedNote - let state: ?SelectedNote - - beforeEach(function () { - dir = '/notes' +import * as actions from "../../lib/action-creators"; +import selectedNoteReducer from "../../lib/reducers/selected-note"; + +describe("reducers/selected-note", () => { + let action: Action; + let dir: string; + let nextSifterResult: SifterResult; + let selectedNote: SelectedNote; + let state: ?SelectedNote; + + beforeEach(function() { + dir = "/notes"; nextSifterResult = { items: [], options: { fields: [], sort: [] }, - query: '', + query: "", tokens: [], total: 0 - } - state = selectedNoteReducer(undefined, actions.startInitialScan(), dir, nextSifterResult) + }; + state = selectedNoteReducer( + undefined, + actions.startInitialScan(), + dir, + nextSifterResult + ); selectedNote = { - filename: 'foo', + filename: "foo", index: 42 - } - }) - - describe('when search', function () { - beforeEach(function () { - state = selectedNoteReducer(selectedNote, actions.search('abc'), dir, nextSifterResult) - }) - - it('should reset selection', function () { - expect(state).toBe(null) - }) - }) - - describe('when reset search', function () { - beforeEach(function () { - state = selectedNoteReducer(selectedNote, actions.search('abc'), dir, nextSifterResult) - }) - - it('should reset selection', function () { - expect(state).toBe(null) - }) - }) - - describe('when change active pane item', function () { - describe('when matches a note', function () { - beforeEach(function () { + }; + }); + + describe("when search", function() { + beforeEach(function() { + state = selectedNoteReducer( + selectedNote, + actions.search("abc"), + dir, + nextSifterResult + ); + }); + + it("should reset selection", function() { + expect(state).toBe(null); + }); + }); + + describe("when reset search", function() { + beforeEach(function() { + state = selectedNoteReducer( + selectedNote, + actions.search("abc"), + dir, + nextSifterResult + ); + }); + + it("should reset selection", function() { + expect(state).toBe(null); + }); + }); + + describe("when change active pane item", function() { + describe("when matches a note", function() { + beforeEach(function() { nextSifterResult.items = [ - {id: 'alice.md', score: 1}, - {id: 'bob.md', score: 1}, - {id: 'cesar.md', score: 1} - ] - state = selectedNoteReducer(selectedNote, actions.changedActivePaneItem('/notes/bob.md'), dir, nextSifterResult) - }) - - it('should select matching item', function () { + { id: "alice.md", score: 1 }, + { id: "bob.md", score: 1 }, + { id: "cesar.md", score: 1 } + ]; + state = selectedNoteReducer( + selectedNote, + actions.changedActivePaneItem("/notes/bob.md"), + dir, + nextSifterResult + ); + }); + + it("should select matching item", function() { expect(state).toEqual({ - filename: 'bob.md', + filename: "bob.md", index: 1 - }) - }) - }) - - describe('when is a non-note file', function () { - beforeEach(function () { - state = selectedNoteReducer(selectedNote, actions.changedActivePaneItem('foo'), dir, nextSifterResult) - }) - - it('should unselect', function () { - expect(state).toBe(null) - }) - }) - }) - - describe('when clicked row', function () { - beforeEach(function () { + }); + }); + }); + + describe("when is a non-note file", function() { + beforeEach(function() { + state = selectedNoteReducer( + selectedNote, + actions.changedActivePaneItem("foo"), + dir, + nextSifterResult + ); + }); + + it("should unselect", function() { + expect(state).toBe(null); + }); + }); + }); + + describe("when clicked row", function() { + beforeEach(function() { nextSifterResult.items = [ - {id: 'alice.md', score: 1}, - {id: 'bob.md', score: 1}, - {id: 'cesar.md', score: 1} - ] - state = selectedNoteReducer(selectedNote, actions.clickRow('bob.md'), dir, nextSifterResult) - }) - - it('should select matching item', function () { + { id: "alice.md", score: 1 }, + { id: "bob.md", score: 1 }, + { id: "cesar.md", score: 1 } + ]; + state = selectedNoteReducer( + selectedNote, + actions.clickRow("bob.md"), + dir, + nextSifterResult + ); + }); + + it("should select matching item", function() { expect(state).toEqual({ - filename: 'bob.md', + filename: "bob.md", index: 1 - }) - }) - }) + }); + }); + }); - describe('when change-sort-field action', function () { - beforeEach(function () { - action = actions.changeSortField('ext') + describe("when change-sort-field action", function() { + beforeEach(function() { + action = actions.changeSortField("ext"); nextSifterResult.items = [ - {id: 'bob.md', score: 1}, - {id: 'cesar.md', score: 1}, - {id: 'alice.txt', score: 1} - ] - }) - - describe('when there is a selected note', function () { - beforeEach(function () { - selectedNote = {index: 0, filename: 'alice.txt'} - state = selectedNoteReducer(selectedNote, action, dir, nextSifterResult) - }) - - it('should update selected index', function () { + { id: "bob.md", score: 1 }, + { id: "cesar.md", score: 1 }, + { id: "alice.txt", score: 1 } + ]; + }); + + describe("when there is a selected note", function() { + beforeEach(function() { + selectedNote = { index: 0, filename: "alice.txt" }; + state = selectedNoteReducer( + selectedNote, + action, + dir, + nextSifterResult + ); + }); + + it("should update selected index", function() { expect(state).toEqual({ index: 2, - filename: 'alice.txt' - }) - }) - }) - - describe('when there is no selected note', function () { - beforeEach(function () { - state = selectedNoteReducer(undefined, action, dir, nextSifterResult) - }) - - it('should not yield any selection', function () { - expect(state).toBe(null) - }) - }) - }) - - describe('when change-sort-direction action', function () { - beforeEach(function () { - action = actions.changeSortDirection('desc') + filename: "alice.txt" + }); + }); + }); + + describe("when there is no selected note", function() { + beforeEach(function() { + state = selectedNoteReducer(undefined, action, dir, nextSifterResult); + }); + + it("should not yield any selection", function() { + expect(state).toBe(null); + }); + }); + }); + + describe("when change-sort-direction action", function() { + beforeEach(function() { + action = actions.changeSortDirection("desc"); nextSifterResult.items = [ - {id: 'cesar.md', score: 1}, - {id: 'bob.md', score: 1}, - {id: 'alice.txt', score: 1} - ] - }) - - describe('when there is a selected note', function () { - beforeEach(function () { - selectedNote = {index: 0, filename: 'alice.txt'} - state = selectedNoteReducer(selectedNote, action, dir, nextSifterResult) - }) - - it('should update selected index', function () { + { id: "cesar.md", score: 1 }, + { id: "bob.md", score: 1 }, + { id: "alice.txt", score: 1 } + ]; + }); + + describe("when there is a selected note", function() { + beforeEach(function() { + selectedNote = { index: 0, filename: "alice.txt" }; + state = selectedNoteReducer( + selectedNote, + action, + dir, + nextSifterResult + ); + }); + + it("should update selected index", function() { expect(state).toEqual({ index: 2, - filename: 'alice.txt' - }) - }) - }) - - describe('when there is no selected note', function () { - beforeEach(function () { - state = selectedNoteReducer(undefined, action, dir, nextSifterResult) - }) - - it('should not yield any selection', function () { - expect(state).toBe(null) - }) - }) - }) - - describe('when select next', function () { - beforeEach(function () { - action = actions.selectNext() + filename: "alice.txt" + }); + }); + }); + + describe("when there is no selected note", function() { + beforeEach(function() { + state = selectedNoteReducer(undefined, action, dir, nextSifterResult); + }); + + it("should not yield any selection", function() { + expect(state).toBe(null); + }); + }); + }); + + describe("when select next", function() { + beforeEach(function() { + action = actions.selectNext(); nextSifterResult.items = [ - {id: 'alice.txt', score: 1}, - {id: 'bob.md', score: 1}, - {id: 'cesar.md', score: 1} - ] - }) - - describe('when there is no selected note', function () { - beforeEach(function () { - state = selectedNoteReducer(undefined, action, dir, nextSifterResult) - }) - - it('should select first item', function () { + { id: "alice.txt", score: 1 }, + { id: "bob.md", score: 1 }, + { id: "cesar.md", score: 1 } + ]; + }); + + describe("when there is no selected note", function() { + beforeEach(function() { + state = selectedNoteReducer(undefined, action, dir, nextSifterResult); + }); + + it("should select first item", function() { expect(state).toEqual({ - filename: 'alice.txt', + filename: "alice.txt", index: 0 - }) - }) + }); + }); - it('should select next item until reaching end of list when called subsequently', function () { - state = selectedNoteReducer(state, action, dir, nextSifterResult) + it("should select next item until reaching end of list when called subsequently", function() { + state = selectedNoteReducer(state, action, dir, nextSifterResult); expect(state).toEqual({ - filename: 'bob.md', + filename: "bob.md", index: 1 - }) + }); - state = selectedNoteReducer(state, action, dir, nextSifterResult) + state = selectedNoteReducer(state, action, dir, nextSifterResult); expect(state).toEqual({ - filename: 'cesar.md', + filename: "cesar.md", index: 2 - }) + }); - state = selectedNoteReducer(state, action, dir, nextSifterResult) - state = selectedNoteReducer(state, action, dir, nextSifterResult) - state = selectedNoteReducer(state, action, dir, nextSifterResult) + state = selectedNoteReducer(state, action, dir, nextSifterResult); + state = selectedNoteReducer(state, action, dir, nextSifterResult); + state = selectedNoteReducer(state, action, dir, nextSifterResult); expect(state).toEqual({ - filename: 'cesar.md', + filename: "cesar.md", index: 2 - }) - }) - }) - }) - - describe('when select prev', function () { - beforeEach(function () { - action = actions.selectPrev() + }); + }); + }); + }); + + describe("when select prev", function() { + beforeEach(function() { + action = actions.selectPrev(); nextSifterResult.items = [ - {id: 'alice.txt', score: 1}, - {id: 'bob.md', score: 1}, - {id: 'cesar.md', score: 1} - ] - }) - - describe('when there is no selected note', function () { - beforeEach(function () { - state = selectedNoteReducer(undefined, action, dir, nextSifterResult) - }) - - it('should select last item', function () { + { id: "alice.txt", score: 1 }, + { id: "bob.md", score: 1 }, + { id: "cesar.md", score: 1 } + ]; + }); + + describe("when there is no selected note", function() { + beforeEach(function() { + state = selectedNoteReducer(undefined, action, dir, nextSifterResult); + }); + + it("should select last item", function() { expect(state).toEqual({ - filename: 'cesar.md', + filename: "cesar.md", index: 2 - }) - }) + }); + }); - it('should select prev item until reaching start of list when called subsequently', function () { - state = selectedNoteReducer(state, action, dir, nextSifterResult) + it("should select prev item until reaching start of list when called subsequently", function() { + state = selectedNoteReducer(state, action, dir, nextSifterResult); expect(state).toEqual({ - filename: 'bob.md', + filename: "bob.md", index: 1 - }) + }); - state = selectedNoteReducer(state, action, dir, nextSifterResult) + state = selectedNoteReducer(state, action, dir, nextSifterResult); expect(state).toEqual({ - filename: 'alice.txt', + filename: "alice.txt", index: 0 - }) + }); - state = selectedNoteReducer(state, action, dir, nextSifterResult) - state = selectedNoteReducer(state, action, dir, nextSifterResult) - state = selectedNoteReducer(state, action, dir, nextSifterResult) + state = selectedNoteReducer(state, action, dir, nextSifterResult); + state = selectedNoteReducer(state, action, dir, nextSifterResult); + state = selectedNoteReducer(state, action, dir, nextSifterResult); expect(state).toEqual({ - filename: 'alice.txt', + filename: "alice.txt", index: 0 - }) - }) - }) - }) - - describe('when called with other action', function () { - describe('when there are items', function () { - beforeEach(function () { - nextSifterResult.items = [ - {id: 'foo', score: 1} - ] - state = selectedNoteReducer(selectedNote, actions.initialScanDone(), dir, nextSifterResult) - }) - - it('should return state', function () { - expect(state).toBe(selectedNote) - }) - }) - - describe('when there are not items', function () { - beforeEach(function () { - state = selectedNoteReducer(selectedNote, actions.initialScanDone(), dir, nextSifterResult) - }) - - it('should return null', function () { - expect(state).toBe(null) - }) - }) - }) -}) + }); + }); + }); + }); + + describe("when called with other action", function() { + describe("when there are items", function() { + beforeEach(function() { + nextSifterResult.items = [{ id: "foo", score: 1 }]; + state = selectedNoteReducer( + selectedNote, + actions.initialScanDone(), + dir, + nextSifterResult + ); + }); + + it("should return state", function() { + expect(state).toBe(selectedNote); + }); + }); + + describe("when there are not items", function() { + beforeEach(function() { + state = selectedNoteReducer( + selectedNote, + actions.initialScanDone(), + dir, + nextSifterResult + ); + }); + + it("should return null", function() { + expect(state).toBe(null); + }); + }); + }); +}); diff --git a/spec/reducers/sifter-result-spec.js b/spec/reducers/sifter-result-spec.js index a9b98bf..d370d84 100644 --- a/spec/reducers/sifter-result-spec.js +++ b/spec/reducers/sifter-result-spec.js @@ -1,202 +1,221 @@ /* @flow */ -import * as A from '../../lib/action-creators' -import NoteFields from '../../lib/note-fields' -import SifterResultReducer from '../../lib/reducers/sifter-result' +import * as A from "../../lib/action-creators"; +import NoteFields from "../../lib/note-fields"; +import SifterResultReducer from "../../lib/reducers/sifter-result"; -describe('reducers/sifter-result', () => { - let state: SifterResult - let notes: Notes - let sifterResultReducer +describe("reducers/sifter-result", () => { + let state: SifterResult; + let notes: Notes; + let sifterResultReducer; - describe('when initial-scan-done action', function () { - beforeEach(function () { - const noteFields = new NoteFields() - noteFields.add({notePropName: 'name'}) - noteFields.add({notePropName: 'ext'}) + describe("when initial-scan-done action", function() { + beforeEach(function() { + const noteFields = new NoteFields(); + noteFields.add({ notePropName: "name" }); + noteFields.add({ notePropName: "ext" }); notes = { - 'alice.md': { - id: '0', - ext: 'md', - name: 'alice', - stats: {mtime: new Date()} + "alice.md": { + id: "0", + ext: "md", + name: "alice", + stats: { mtime: new Date() } }, - 'bob.md': { - id: '1', - ext: 'md', - name: 'bob', - stats: {mtime: new Date()} + "bob.md": { + id: "1", + ext: "md", + name: "bob", + stats: { mtime: new Date() } }, - 'cesar.txt': { - id: '2', - ext: 'txt', - name: 'cesar', - stats: {mtime: new Date()} + "cesar.txt": { + id: "2", + ext: "txt", + name: "cesar", + stats: { mtime: new Date() } }, - 'david.txt': { - id: '3', - ext: 'txt', - name: 'david', - stats: {mtime: new Date()} + "david.txt": { + id: "3", + ext: "txt", + name: "david", + stats: { mtime: new Date() } }, - 'eric.md': { - id: '4', - ext: 'md', - name: 'eric', - stats: {mtime: new Date()} + "eric.md": { + id: "4", + ext: "md", + name: "eric", + stats: { mtime: new Date() } } - } - - sifterResultReducer = SifterResultReducer(noteFields) - state = sifterResultReducer(undefined, A.startInitialScan(), notes) - }) - - describe('when initial scan is done', function () { - beforeEach(function () { - state = sifterResultReducer(state, A.initialScanDone(), notes) - }) - - it('should return results for empty query', function () { - expect(state.query).toEqual('') - expect(state.items.length).toBeGreaterThan(0) - }) - }) - - describe('when reset search', function () { - beforeEach(function () { - state = sifterResultReducer(state, A.resetSearch(), notes) - }) - - it('should return results for empty query', function () { - expect(state.query).toEqual('') - expect(state.items.length).toBeGreaterThan(0) - }) - }) - - describe('when changed sort field', function () { - beforeEach(function () { - state = sifterResultReducer(state, A.changeSortField('name'), notes) - }) - - it('should return results', function () { - expect(state.items.length).toBeGreaterThan(0) - expect(state.query).toEqual('') - }) - - it('should order by new sort field', function () { - const ids = state.items.map(x => x.id) - expect(ids).toEqual(ids.sort()) - }) - }) - - describe('when changed sort direction', function () { - beforeEach(function () { - state = sifterResultReducer(state, A.changeSortDirection('desc'), notes) - }) - - it('should return results', function () { - expect(state.items.length).toBeGreaterThan(0) - expect(state.query).toEqual('') - }) - - it('should order by new sort direction', function () { - const ids = state.items.map(x => x.id) - expect(ids).toEqual(ids.sort().reverse()) - }) - }) - - describe('when search', function () { - beforeEach(function () { - state = sifterResultReducer(state, A.search('md'), notes) - }) - - it('should update query', function () { - expect(state.query).toEqual('md') - }) - - it('should return results matching query', function () { - expect(state.total).toEqual(3) - expect(state.items.length).toEqual(3) - expect(state.items[0].id).toEqual('alice.md') - }) - - it('should return regexp for matched token', function () { - expect(state.tokens).toEqual(jasmine.any(Array)) + }; + + sifterResultReducer = SifterResultReducer(noteFields); + state = sifterResultReducer(undefined, A.startInitialScan(), notes); + }); + + describe("when initial scan is done", function() { + beforeEach(function() { + state = sifterResultReducer(state, A.initialScanDone(), notes); + }); + + it("should return results for empty query", function() { + expect(state.query).toEqual(""); + expect(state.items.length).toBeGreaterThan(0); + }); + }); + + describe("when reset search", function() { + beforeEach(function() { + state = sifterResultReducer(state, A.resetSearch(), notes); + }); + + it("should return results for empty query", function() { + expect(state.query).toEqual(""); + expect(state.items.length).toBeGreaterThan(0); + }); + }); + + describe("when changed sort field", function() { + beforeEach(function() { + state = sifterResultReducer(state, A.changeSortField("name"), notes); + }); + + it("should return results", function() { + expect(state.items.length).toBeGreaterThan(0); + expect(state.query).toEqual(""); + }); + + it("should order by new sort field", function() { + const ids = state.items.map(x => x.id); + expect(ids).toEqual(ids.sort()); + }); + }); + + describe("when changed sort direction", function() { + beforeEach(function() { + state = sifterResultReducer( + state, + A.changeSortDirection("desc"), + notes + ); + }); + + it("should return results", function() { + expect(state.items.length).toBeGreaterThan(0); + expect(state.query).toEqual(""); + }); + + it("should order by new sort direction", function() { + const ids = state.items.map(x => x.id); + expect(ids).toEqual(ids.sort().reverse()); + }); + }); + + describe("when search", function() { + beforeEach(function() { + state = sifterResultReducer(state, A.search("md"), notes); + }); + + it("should update query", function() { + expect(state.query).toEqual("md"); + }); + + it("should return results matching query", function() { + expect(state.total).toEqual(3); + expect(state.items.length).toEqual(3); + expect(state.items[0].id).toEqual("alice.md"); + }); + + it("should return regexp for matched token", function() { + expect(state.tokens).toEqual(jasmine.any(Array)); expect(state.tokens[0]).toEqual({ - string: 'md', + string: "md", regex: jasmine.any(RegExp) + }); + }); + + it("should have default sort", function() { + expect(state.options.sort[0]).toEqual({ + field: "$score", + direction: "desc" + }); + }); + + describe("when sort have been set", function() { + beforeEach(function() { + state.options.sort.unshift({ field: "name", direction: "asc" }); + state = sifterResultReducer(state, A.search("md"), notes); + }); + + it("should use sort defined by state", function() { + expect(state.options.sort[0]).toEqual({ + field: "name", + direction: "asc" + }); + }); + + it("should use default sort as secondary fallback", function() { + expect(state.options.sort[1]).toEqual({ + field: "$score", + direction: "desc" + }); + }); + }); + }); + + describe("when add file", function() { + sharedUpdateSearchFile( + A.fileAdded({ + filename: "alice.txt", + stats: { mtime: new Date() } }) - }) - - it('should have default sort', function () { - expect(state.options.sort[0]).toEqual({field: '$score', direction: 'desc'}) - }) - - describe('when sort have been set', function () { - beforeEach(function () { - state.options.sort.unshift({field: 'name', direction: 'asc'}) - state = sifterResultReducer(state, A.search('md'), notes) + ); + }); + + describe("when changed file", function() { + sharedUpdateSearchFile( + A.fileChanged({ + filename: "alice.txt", + stats: { mtime: new Date() } }) - - it('should use sort defined by state', function () { - expect(state.options.sort[0]).toEqual({field: 'name', direction: 'asc'}) - }) - - it('should use default sort as secondary fallback', function () { - expect(state.options.sort[1]).toEqual({field: '$score', direction: 'desc'}) + ); + }); + + describe("when read file", function() { + sharedUpdateSearchFile( + A.fileRead({ + filename: "alice.txt", + notePropName: "content", + value: "content for alice.txt" }) - }) - }) - - describe('when add file', function () { - sharedUpdateSearchFile(A.fileAdded({ - filename: 'alice.txt', - stats: {mtime: new Date()} - })) - }) - - describe('when changed file', function () { - sharedUpdateSearchFile(A.fileChanged({ - filename: 'alice.txt', - stats: {mtime: new Date()} - })) - }) - - describe('when read file', function () { - sharedUpdateSearchFile(A.fileRead({ - filename: 'alice.txt', - notePropName: 'content', - value: 'content for alice.txt' - })) - }) - - describe('when any other action', function () { - let prevState - - beforeEach(function () { - prevState = state - state = sifterResultReducer(state, A.scroll(0), notes) - }) - - it('should return prev state', function () { - expect(state).toBe(prevState) - }) - }) - - function sharedUpdateSearchFile (action: Action) { - let prevState - - beforeEach(function () { - prevState = state - state.query = 'abc' - state = sifterResultReducer(state, action, notes) - }) - - it('should update search w/ existing query', function () { - expect(state).not.toBe(prevState) - expect(state.query).toEqual('abc') - }) + ); + }); + + describe("when any other action", function() { + let prevState; + + beforeEach(function() { + prevState = state; + state = sifterResultReducer(state, A.scroll(0), notes); + }); + + it("should return prev state", function() { + expect(state).toBe(prevState); + }); + }); + + function sharedUpdateSearchFile(action: Action) { + let prevState; + + beforeEach(function() { + prevState = state; + state.query = "abc"; + state = sifterResultReducer(state, action, notes); + }); + + it("should update search w/ existing query", function() { + expect(state).not.toBe(prevState); + expect(state.query).toEqual("abc"); + }); } - }) -}) + }); +}); diff --git a/spec/reselectors/pagination-spec.js b/spec/reselectors/pagination-spec.js index f9e4bd5..c291a9f 100644 --- a/spec/reselectors/pagination-spec.js +++ b/spec/reselectors/pagination-spec.js @@ -1,15 +1,15 @@ /* @flow */ -import paginationSelector from '../../lib/reselectors/pagination' +import paginationSelector from "../../lib/reselectors/pagination"; -describe('reselectors/pagination', () => { - let state: State - let pagination: Pagination +describe("reselectors/pagination", () => { + let state: State; + let pagination: Pagination; - beforeEach(function () { + beforeEach(function() { state = { columnHeaders: [], - dir: '', + dir: "", editCellName: null, initialScan: { done: false, @@ -17,7 +17,7 @@ describe('reselectors/pagination', () => { }, listHeight: 1000, notes: {}, - queryOriginal: '', + queryOriginal: "", rowHeight: 24, scrollTop: 0, selectedNote: null, @@ -26,35 +26,35 @@ describe('reselectors/pagination', () => { options: { fields: [], sort: [ - {field: 'name', direction: 'asc'}, - {field: '$score', direction: 'desc'} + { field: "name", direction: "asc" }, + { field: "$score", direction: "desc" } ] }, - query: '', + query: "", tokens: [], total: 0 } - } - }) + }; + }); - it('should update pagination according to state', function () { - pagination = paginationSelector(state) - expect(pagination.start).toEqual(0) - expect(pagination.limit).toEqual(43) // 41 +2 for visible padding + it("should update pagination according to state", function() { + pagination = paginationSelector(state); + expect(pagination.start).toEqual(0); + expect(pagination.limit).toEqual(43); // 41 +2 for visible padding - state = {...state, scrollTop: 50} - pagination = paginationSelector(state) - expect(pagination.start).toEqual(2) // 2,5, rounded down - expect(pagination.limit).toEqual(43) // 41 +2 for visible padding + state = { ...state, scrollTop: 50 }; + pagination = paginationSelector(state); + expect(pagination.start).toEqual(2); // 2,5, rounded down + expect(pagination.limit).toEqual(43); // 41 +2 for visible padding - state = {...state, listHeight: 100} - pagination = paginationSelector(state) - expect(pagination.start).toEqual(2) // 2,5, rounded down - expect(pagination.limit).toEqual(6) // 4.1 rounded down +2 for visible padding + state = { ...state, listHeight: 100 }; + pagination = paginationSelector(state); + expect(pagination.start).toEqual(2); // 2,5, rounded down + expect(pagination.limit).toEqual(6); // 4.1 rounded down +2 for visible padding - state = {...state, rowHeight: 20} - pagination = paginationSelector(state) - expect(pagination.start).toEqual(2) // 2,5, rounded down - expect(pagination.limit).toEqual(7) // 5 +2 for visible padding - }) -}) + state = { ...state, rowHeight: 20 }; + pagination = paginationSelector(state); + expect(pagination.start).toEqual(2); // 2,5, rounded down + expect(pagination.limit).toEqual(7); // 5 +2 for visible padding + }); +}); diff --git a/spec/reselectors/visible-rows-spec.js b/spec/reselectors/visible-rows-spec.js index 8a6365c..5207e0c 100644 --- a/spec/reselectors/visible-rows-spec.js +++ b/spec/reselectors/visible-rows-spec.js @@ -1,24 +1,24 @@ /* @flow */ -import Columns from '../../lib/columns' -import FileIconColumn from '../../lib/columns/file-icon-column' -import SummaryColumn from '../../lib/columns/summary-column' -import makeVisibleRowsSelector from '../../lib/reselectors/visible-rows' -import paginationSelector from '../../lib/reselectors/pagination' - -describe('reselectors/visible-rows', () => { - let state: State - let visibleRows: Array - let visibleRowsSelector - - beforeEach(function () { - const columns = new Columns() - columns.add(new SummaryColumn({sortField: 'name', editCellName: ''})) - columns.add(new FileIconColumn({sortField: 'ext'})) +import Columns from "../../lib/columns"; +import FileIconColumn from "../../lib/columns/file-icon-column"; +import SummaryColumn from "../../lib/columns/summary-column"; +import makeVisibleRowsSelector from "../../lib/reselectors/visible-rows"; +import paginationSelector from "../../lib/reselectors/pagination"; + +describe("reselectors/visible-rows", () => { + let state: State; + let visibleRows: Array; + let visibleRowsSelector; + + beforeEach(function() { + const columns = new Columns(); + columns.add(new SummaryColumn({ sortField: "name", editCellName: "" })); + columns.add(new FileIconColumn({ sortField: "ext" })); state = { columnHeaders: [], - dir: '/notes', + dir: "/notes", editCellName: null, initialScan: { done: false, @@ -26,179 +26,211 @@ describe('reselectors/visible-rows', () => { }, listHeight: 25, notes: { - 'alice.txt': { - id: '0', - ext: 'txt', - name: 'alice', - path: '/notes/alice.txt', - stats: {mtime: new Date()} + "alice.txt": { + id: "0", + ext: "txt", + name: "alice", + path: "/notes/alice.txt", + stats: { mtime: new Date() } }, - 'bob.md': { - id: '1', - ext: 'md', - name: 'bob', - path: '/notes/bob.md', - stats: {mtime: new Date()} + "bob.md": { + id: "1", + ext: "md", + name: "bob", + path: "/notes/bob.md", + stats: { mtime: new Date() } }, - 'cesar.txt': { - id: '2', - ext: 'txt', - name: 'cesar', - path: '/notes/cesar.txt', - stats: {mtime: new Date()} + "cesar.txt": { + id: "2", + ext: "txt", + name: "cesar", + path: "/notes/cesar.txt", + stats: { mtime: new Date() } }, - 'david.md': { - id: '3', - ext: 'md', - name: 'david', - path: '/notes/david.md', - stats: {mtime: new Date()} + "david.md": { + id: "3", + ext: "md", + name: "david", + path: "/notes/david.md", + stats: { mtime: new Date() } }, - 'eric.txt': { - id: '4', - ext: 'txt', - name: 'eric', - path: '/notes/eric.txt', - stats: {mtime: new Date()} + "eric.txt": { + id: "4", + ext: "txt", + name: "eric", + path: "/notes/eric.txt", + stats: { mtime: new Date() } } }, - queryOriginal: '', + queryOriginal: "", rowHeight: 25, scrollTop: 0, selectedNote: null, sifterResult: { items: [ - {id: 'alice.txt', score: 1.0}, - {id: 'bob.md', score: 0.9}, - {id: 'cesar.txt', score: 0.9}, - {id: 'david.md', score: 0.8}, - {id: 'eric.txt', score: 0.7} + { id: "alice.txt", score: 1.0 }, + { id: "bob.md", score: 0.9 }, + { id: "cesar.txt", score: 0.9 }, + { id: "david.md", score: 0.8 }, + { id: "eric.txt", score: 0.7 } ], options: { - fields: ['name', 'ext'], + fields: ["name", "ext"], sort: [ - {field: 'name', direction: 'asc'}, - {field: '$score', direction: 'desc'} + { field: "name", direction: "asc" }, + { field: "$score", direction: "desc" } ] }, - query: '', + query: "", tokens: [], total: 5 } - } - - visibleRowsSelector = makeVisibleRowsSelector(columns, paginationSelector) - }) - - describe('when initial scan is done', function () { - beforeEach(function () { - visibleRows = visibleRowsSelector(state) - }) - - it('should return paginated rows', function () { - expect(visibleRows).toEqual(jasmine.any(Array)) - expect(visibleRows.map(x => x.id)).toEqual(['0', '1', '2']) - expect(visibleRows.map(x => x.filename)).toEqual(['alice.txt', 'bob.md', 'cesar.txt']) - expect(visibleRows.map(x => x.cells)).toEqual(jasmine.any(Array)) - }) - }) - - describe('when have search', function () { - beforeEach(function () { - state.queryOriginal = 'A' // matches Alice, cesAr, dAvid - state.sifterResult.query = 'a' + }; + + visibleRowsSelector = makeVisibleRowsSelector(columns, paginationSelector); + }); + + describe("when initial scan is done", function() { + beforeEach(function() { + visibleRows = visibleRowsSelector(state); + }); + + it("should return paginated rows", function() { + expect(visibleRows).toEqual(jasmine.any(Array)); + expect(visibleRows.map(x => x.id)).toEqual(["0", "1", "2"]); + expect(visibleRows.map(x => x.filename)).toEqual([ + "alice.txt", + "bob.md", + "cesar.txt" + ]); + expect(visibleRows.map(x => x.cells)).toEqual(jasmine.any(Array)); + }); + }); + + describe("when have search", function() { + beforeEach(function() { + state.queryOriginal = "A"; // matches Alice, cesAr, dAvid + state.sifterResult.query = "a"; state.sifterResult.items = [ - {id: 'alice.txt', score: 0.1}, - {id: 'cesar.txt', score: 0.1}, - {id: 'david.md', score: 0.1} - ] - visibleRows = visibleRowsSelector(state) - }) - - it('should return paginated rows', function () { - expect(visibleRows).toEqual(jasmine.any(Array)) - expect(visibleRows.map(x => x.id)).toEqual(['0', '2', '3']) - expect(visibleRows.map(x => x.filename)).toEqual(['alice.txt', 'cesar.txt', 'david.md']) - expect(visibleRows.map(x => x.cells)).toEqual(jasmine.any(Array)) - }) - }) - - describe('when scrolled', function () { - beforeEach(function () { - state.scrollTop = 25 - visibleRows = visibleRowsSelector(state) - }) - - it('should return paginated rows', function () { - expect(visibleRows).toEqual(jasmine.any(Array)) - expect(visibleRows.map(x => x.id)).toEqual(['1', '2', '3']) - expect(visibleRows.map(x => x.filename)).toEqual(['bob.md', 'cesar.txt', 'david.md']) - expect(visibleRows.map(x => x.cells)).toEqual(jasmine.any(Array)) - }) - }) - - describe('when changed list height', function () { - beforeEach(function () { - state.listHeight = 1001 - visibleRows = visibleRowsSelector(state) - }) - - it('should return paginated rows', function () { - expect(visibleRows).toEqual(jasmine.any(Array)) - expect(visibleRows.map(x => x.id)).toEqual(['0', '1', '2', '3', '4']) - expect(visibleRows.map(x => x.filename)).toEqual(['alice.txt', 'bob.md', 'cesar.txt', 'david.md', 'eric.txt']) - expect(visibleRows.map(x => x.cells)).toEqual(jasmine.any(Array)) - }) - }) - - describe('when changed row height', function () { - beforeEach(function () { - state.rowHeight = 12 - visibleRows = visibleRowsSelector(state) - }) - - it('should return paginated rows', function () { - expect(visibleRows).toEqual(jasmine.any(Array)) - expect(visibleRows.map(x => x.id)).toEqual(['0', '1', '2', '3']) - expect(visibleRows.map(x => x.filename)).toEqual(['alice.txt', 'bob.md', 'cesar.txt', 'david.md']) - expect(visibleRows.map(x => x.cells)).toEqual(jasmine.any(Array)) - }) - }) - - describe('when changed sort direction', function () { - beforeEach(function () { - state.sifterResult.options.sort[0].direction = 'desc' - state.sifterResult.items = state.sifterResult.items.reverse() - visibleRows = visibleRowsSelector(state) - }) - - it('should return paginated rows', function () { - expect(visibleRows).toEqual(jasmine.any(Array)) - expect(visibleRows.map(x => x.id)).toEqual(['4', '3', '2']) - expect(visibleRows.map(x => x.filename)).toEqual(['eric.txt', 'david.md', 'cesar.txt']) - expect(visibleRows.map(x => x.cells)).toEqual(jasmine.any(Array)) - }) - }) - - describe('when changed sort field', function () { - beforeEach(function () { - state.sifterResult.options.sort[0].field = 'ext' - state.sifterResult.items = state.sifterResult.items - .sort((a: any, b: any) => { - a = a.id.split('.')[1] - b = b.id.split('.')[1] - if (a < b) return -1 - if (a > b) return 1 - return 0 // equal - }) - visibleRows = visibleRowsSelector(state) - }) - - it('should return paginated rows', function () { - expect(visibleRows).toEqual(jasmine.any(Array)) - expect(visibleRows.map(x => x.id)).toEqual(['1', '3', '0']) - expect(visibleRows.map(x => x.filename)).toEqual(['bob.md', 'david.md', 'alice.txt']) - expect(visibleRows.map(x => x.cells)).toEqual(jasmine.any(Array)) - }) - }) -}) + { id: "alice.txt", score: 0.1 }, + { id: "cesar.txt", score: 0.1 }, + { id: "david.md", score: 0.1 } + ]; + visibleRows = visibleRowsSelector(state); + }); + + it("should return paginated rows", function() { + expect(visibleRows).toEqual(jasmine.any(Array)); + expect(visibleRows.map(x => x.id)).toEqual(["0", "2", "3"]); + expect(visibleRows.map(x => x.filename)).toEqual([ + "alice.txt", + "cesar.txt", + "david.md" + ]); + expect(visibleRows.map(x => x.cells)).toEqual(jasmine.any(Array)); + }); + }); + + describe("when scrolled", function() { + beforeEach(function() { + state.scrollTop = 25; + visibleRows = visibleRowsSelector(state); + }); + + it("should return paginated rows", function() { + expect(visibleRows).toEqual(jasmine.any(Array)); + expect(visibleRows.map(x => x.id)).toEqual(["1", "2", "3"]); + expect(visibleRows.map(x => x.filename)).toEqual([ + "bob.md", + "cesar.txt", + "david.md" + ]); + expect(visibleRows.map(x => x.cells)).toEqual(jasmine.any(Array)); + }); + }); + + describe("when changed list height", function() { + beforeEach(function() { + state.listHeight = 1001; + visibleRows = visibleRowsSelector(state); + }); + + it("should return paginated rows", function() { + expect(visibleRows).toEqual(jasmine.any(Array)); + expect(visibleRows.map(x => x.id)).toEqual(["0", "1", "2", "3", "4"]); + expect(visibleRows.map(x => x.filename)).toEqual([ + "alice.txt", + "bob.md", + "cesar.txt", + "david.md", + "eric.txt" + ]); + expect(visibleRows.map(x => x.cells)).toEqual(jasmine.any(Array)); + }); + }); + + describe("when changed row height", function() { + beforeEach(function() { + state.rowHeight = 12; + visibleRows = visibleRowsSelector(state); + }); + + it("should return paginated rows", function() { + expect(visibleRows).toEqual(jasmine.any(Array)); + expect(visibleRows.map(x => x.id)).toEqual(["0", "1", "2", "3"]); + expect(visibleRows.map(x => x.filename)).toEqual([ + "alice.txt", + "bob.md", + "cesar.txt", + "david.md" + ]); + expect(visibleRows.map(x => x.cells)).toEqual(jasmine.any(Array)); + }); + }); + + describe("when changed sort direction", function() { + beforeEach(function() { + state.sifterResult.options.sort[0].direction = "desc"; + state.sifterResult.items = state.sifterResult.items.reverse(); + visibleRows = visibleRowsSelector(state); + }); + + it("should return paginated rows", function() { + expect(visibleRows).toEqual(jasmine.any(Array)); + expect(visibleRows.map(x => x.id)).toEqual(["4", "3", "2"]); + expect(visibleRows.map(x => x.filename)).toEqual([ + "eric.txt", + "david.md", + "cesar.txt" + ]); + expect(visibleRows.map(x => x.cells)).toEqual(jasmine.any(Array)); + }); + }); + + describe("when changed sort field", function() { + beforeEach(function() { + state.sifterResult.options.sort[0].field = "ext"; + state.sifterResult.items = state.sifterResult.items.sort( + (a: any, b: any) => { + a = a.id.split(".")[1]; + b = b.id.split(".")[1]; + if (a < b) return -1; + if (a > b) return 1; + return 0; // equal + } + ); + visibleRows = visibleRowsSelector(state); + }); + + it("should return paginated rows", function() { + expect(visibleRows).toEqual(jasmine.any(Array)); + expect(visibleRows.map(x => x.id)).toEqual(["1", "3", "0"]); + expect(visibleRows.map(x => x.filename)).toEqual([ + "bob.md", + "david.md", + "alice.txt" + ]); + expect(visibleRows.map(x => x.cells)).toEqual(jasmine.any(Array)); + }); + }); +}); diff --git a/spec/service-consumers/nv-tags-spec.js b/spec/service-consumers/nv-tags-spec.js index 4e25af1..f54f0df 100644 --- a/spec/service-consumers/nv-tags-spec.js +++ b/spec/service-consumers/nv-tags-spec.js @@ -1,151 +1,152 @@ -var Path = require('path') -var temp = require('temp').track() -var Service = require('../../lib/service') -var nvTags = require('../../lib/service-consumers/nv-tags') +var Path = require("path"); +var temp = require("temp").track(); +var Service = require("../../lib/service"); +var nvTags = require("../../lib/service-consumers/nv-tags"); -var unsupErr = nvTags.getUnsupportedError() +var unsupErr = nvTags.getUnsupportedError(); // tests for platforms where nvtags are unavailable -describe('service-consumers/nv-tags', function () { - it('should return a disposable object even if tags will not be loaded', function () { - var service = new Service() - spyOn(nvTags, 'getUnsupportedError').andReturn('not supported') - var disposable = nvTags.consumeService(service) - expect(disposable.dispose).toEqual(jasmine.any(Function)) - }) -}) +describe("service-consumers/nv-tags", function() { + it("should return a disposable object even if tags will not be loaded", function() { + var service = new Service(); + spyOn(nvTags, "getUnsupportedError").andReturn("not supported"); + var disposable = nvTags.consumeService(service); + expect(disposable.dispose).toEqual(jasmine.any(Function)); + }); +}); if (unsupErr) { - console.log('nv-tags-specs will not run, see logged error on next line:') - console.warn(unsupErr) - return // skip the rest of the tests if could not load tags + console.log("nv-tags-specs will not run, see logged error on next line:"); + console.warn(unsupErr); +} else { + // tests for platforms where nvtags are available + describe("service-consumers/nv-tags", function() { + var disposable, service; + + beforeEach(function() { + service = new Service(); // integration tested through main-spec.js and CI env + spyOn(service, "registerColumns"); + spyOn(service, "registerFields"); + spyOn(service, "registerFileReaders"); + spyOn(service, "registerFileWriters"); + + disposable = nvTags.consumeService(service); + expect(service.registerColumns).toHaveBeenCalled(); + expect(service.registerFields).toHaveBeenCalled(); + expect(service.registerFileReaders).toHaveBeenCalled(); + expect(service.registerFileWriters).toHaveBeenCalled(); + }); + + afterEach(function() { + disposable.dispose(); + }); + + describe("registered field", function() { + var field; + + beforeEach(function() { + field = service.registerFields.mostRecentCall.args[0]; + }); + + describe(".value", function() { + it("should return the tags as a space separated string", function() { + expect(field.value({ nvtags: ["beep", "boop"] })).toEqual( + "beep boop" + ); + }); + + it("should return nothing for nonvalid prop", function() { + expect(field.value({ nvtags: {} })).toBeFalsy(); + expect(field.value({ nvtags: null })).toBeFalsy(); + }); + }); + }); + + describe("registered column", function() { + var column; + + beforeEach(function() { + column = service.registerColumns.mostRecentCall.args[0]; + }); + + describe(".cellContent", function() { + it("should return the tags as a space separated string", function() { + var note = { nvtags: ["beep", "boop"] }; + var cellContent = column.cellContent({ note: note }); + + expect(cellContent).toEqual(jasmine.any(Array)); + expect(cellContent[0]).toEqual({ + attrs: jasmine.any(Object), + content: "beep" + }); + }); + + it("should return nothing for nonvalid prop", function() { + expect(column.cellContent({ note: { nvtags: {} } })).toBeFalsy(); + expect(column.cellContent({ note: { nvtags: null } })).toBeFalsy(); + }); + }); + }); + + describe("registered file reader+writer", function() { + var fileReader, fileWriter, path, callback; + var fileStats = {}; + + beforeEach(function() { + fileReader = service.registerFileReaders.mostRecentCall.args[0]; + fileWriter = service.registerFileWriters.mostRecentCall.args[0]; + callback = jasmine.createSpy("callback"); + }); + + it("should write/read tags to file of given path", function() { + var readSpy = jasmine.createSpy("fileReader.read"); + var writeSpy = jasmine.createSpy("fileWriter.write"); + var tmpFile = temp.createWriteStream("tmp"); + tmpFile.write("foo"); + tmpFile.end(); + fileWriter.write(tmpFile.path, "beep boop boop beep", writeSpy); + + waitsFor(function() { + return writeSpy.calls.length >= 1; + }); + runs(function() { + expect(writeSpy.mostRecentCall.args[0]).toBeFalsy(); + expect(writeSpy.mostRecentCall.args[1]).toBeFalsy(); + fileReader.read(tmpFile.path, fileStats, readSpy); + }); + + waitsFor(function() { + return readSpy.calls.length >= 1; + }); + runs(function() { + expect(readSpy.mostRecentCall.args[0]).toBeFalsy(); + expect(readSpy.mostRecentCall.args[1]).toEqual(["beep", "boop"]); + }); + }); + + it("should return null for a read file that have no xattrs set", function() { + path = Path.join(__dirname, "..", "fixtures", "standard", "empty.md"); + fileReader.read(path, fileStats, callback); + waitsFor(function() { + return callback.calls.length >= 1; + }); + runs(function() { + expect(callback.mostRecentCall.args[0]).toBeFalsy(); + expect(callback.mostRecentCall.args[1]).toEqual(null); + }); + }); + + it("should return error if read file does not exist", function() { + fileReader.read("nonexisting", fileStats, callback); + waitsFor(function() { + return callback.calls.length >= 1; + }); + runs(function() { + expect(callback.mostRecentCall.args[0]).toBeDefined(); + expect(callback.mostRecentCall.args[0].code).toEqual("ENOENT"); + expect(callback.mostRecentCall.args[1]).toBeFalsy(); + }); + }); + }); + }); } - -// tests for platforms where nvtags are available -describe('service-consumers/nv-tags', function () { - var disposable, service - - beforeEach(function () { - service = new Service() // integration tested through main-spec.js and CI env - spyOn(service, 'registerColumns') - spyOn(service, 'registerFields') - spyOn(service, 'registerFileReaders') - spyOn(service, 'registerFileWriters') - - disposable = nvTags.consumeService(service) - expect(service.registerColumns).toHaveBeenCalled() - expect(service.registerFields).toHaveBeenCalled() - expect(service.registerFileReaders).toHaveBeenCalled() - expect(service.registerFileWriters).toHaveBeenCalled() - }) - - afterEach(function () { - disposable.dispose() - }) - - describe('registered field', function () { - var field - - beforeEach(function () { - field = service.registerFields.mostRecentCall.args[0] - }) - - describe('.value', function () { - it('should return the tags as a space separated string', function () { - expect(field.value({nvtags: ['beep', 'boop']})).toEqual('beep boop') - }) - - it('should return nothing for nonvalid prop', function () { - expect(field.value({nvtags: {}})).toBeFalsy() - expect(field.value({nvtags: null})).toBeFalsy() - }) - }) - }) - - describe('registered column', function () { - var column - - beforeEach(function () { - column = service.registerColumns.mostRecentCall.args[0] - }) - - describe('.cellContent', function () { - it('should return the tags as a space separated string', function () { - var note = {nvtags: ['beep', 'boop']} - var cellContent = column.cellContent({note: note}) - - expect(cellContent).toEqual(jasmine.any(Array)) - expect(cellContent[0]).toEqual({ - attrs: jasmine.any(Object), - content: 'beep' - }) - }) - - it('should return nothing for nonvalid prop', function () { - expect(column.cellContent({note: {nvtags: {}}})).toBeFalsy() - expect(column.cellContent({note: {nvtags: null}})).toBeFalsy() - }) - }) - }) - - describe('registered file reader+writer', function () { - var fileReader, fileWriter, path, callback - var fileStats = {} - - beforeEach(function () { - fileReader = service.registerFileReaders.mostRecentCall.args[0] - fileWriter = service.registerFileWriters.mostRecentCall.args[0] - callback = jasmine.createSpy('callback') - }) - - it('should write/read tags to file of given path', function () { - var readSpy = jasmine.createSpy('fileReader.read') - var writeSpy = jasmine.createSpy('fileWriter.write') - var tmpFile = temp.createWriteStream('tmp') - tmpFile.write('foo') - tmpFile.end() - fileWriter.write(tmpFile.path, 'beep boop boop beep', writeSpy) - - waitsFor(function () { - return writeSpy.calls.length >= 1 - }) - runs(function () { - expect(writeSpy.mostRecentCall.args[0]).toBeFalsy() - expect(writeSpy.mostRecentCall.args[1]).toBeFalsy() - fileReader.read(tmpFile.path, fileStats, readSpy) - }) - - waitsFor(function () { - return readSpy.calls.length >= 1 - }) - runs(function () { - expect(readSpy.mostRecentCall.args[0]).toBeFalsy() - expect(readSpy.mostRecentCall.args[1]).toEqual(['beep', 'boop']) - }) - }) - - it('should return null for a read file that have no xattrs set', function () { - path = Path.join(__dirname, '..', 'fixtures', 'standard', 'empty.md') - fileReader.read(path, fileStats, callback) - waitsFor(function () { - return callback.calls.length >= 1 - }) - runs(function () { - expect(callback.mostRecentCall.args[0]).toBeFalsy() - expect(callback.mostRecentCall.args[1]).toEqual(null) - }) - }) - - it('should return error if read file does not exist', function () { - fileReader.read('nonexisting', fileStats, callback) - waitsFor(function () { - return callback.calls.length >= 1 - }) - runs(function () { - expect(callback.mostRecentCall.args[0]).toBeDefined() - expect(callback.mostRecentCall.args[0].code).toEqual('ENOENT') - expect(callback.mostRecentCall.args[1]).toBeFalsy() - }) - }) - }) -}) diff --git a/spec/service-consumers/rename-note-spec.js b/spec/service-consumers/rename-note-spec.js index 4928019..aaa0b6d 100644 --- a/spec/service-consumers/rename-note-spec.js +++ b/spec/service-consumers/rename-note-spec.js @@ -1,75 +1,81 @@ /* @flow */ -import fs from 'fs' -import Path from 'path' -import renameNote from '../../lib/service-consumers/rename-note' +import fs from "fs"; +import Path from "path"; +import renameNote from "../../lib/service-consumers/rename-note"; -describe('service-consumers/rename-note', function () { - describe('.consumeService', function () { - const editCellName = 'bampadam' - let disposable, service +describe("service-consumers/rename-note", function() { + describe(".consumeService", function() { + const editCellName = "bampadam"; + let disposable, service; - beforeEach(function () { + beforeEach(function() { service = { - registerColumns: jasmine.createSpy('registerColumns'), - registerFields: jasmine.createSpy('registerFields'), - registerFileReaders: jasmine.createSpy('registerFileReaders'), - deregisterFileReaders: jasmine.createSpy('deregisterFileReaders'), - registerFileWriters: jasmine.createSpy('registerFileWriters'), - editCell: jasmine.createSpy('editCell') - } - disposable = renameNote.consumeService(service, editCellName) - }) + registerColumns: jasmine.createSpy("registerColumns"), + registerFields: jasmine.createSpy("registerFields"), + registerFileReaders: jasmine.createSpy("registerFileReaders"), + deregisterFileReaders: jasmine.createSpy("deregisterFileReaders"), + registerFileWriters: jasmine.createSpy("registerFileWriters"), + editCell: jasmine.createSpy("editCell") + }; + disposable = renameNote.consumeService(service, editCellName); + }); - afterEach(function () { - disposable.dispose() - }) + afterEach(function() { + disposable.dispose(); + }); - describe('registered file writer', function () { - let fileWriter + describe("registered file writer", function() { + let fileWriter; - beforeEach(function () { - expect(service.registerFileWriters).toHaveBeenCalled() - fileWriter = service.registerFileWriters.mostRecentCall.args[0] - }) + beforeEach(function() { + expect(service.registerFileWriters).toHaveBeenCalled(); + fileWriter = service.registerFileWriters.mostRecentCall.args[0]; + }); - it('should have a edit cell name', function () { - expect(fileWriter.editCellName).toEqual(editCellName) - }) + it("should have a edit cell name", function() { + expect(fileWriter.editCellName).toEqual(editCellName); + }); - describe('.write', function () { - let callbackSpy - let oldPath + describe(".write", function() { + let callbackSpy; + let oldPath; - beforeEach(function () { - oldPath = '/path/to/notes/old-file-path.txt' - spyOn(fs, 'renameSync') - spyOn(fs, 'utimesSync') - callbackSpy = jasmine.createSpy('callback') - }) + beforeEach(function() { + oldPath = "/path/to/notes/old-file-path.txt"; + spyOn(fs, "renameSync"); + spyOn(fs, "utimesSync"); + callbackSpy = jasmine.createSpy("callback"); + }); - it('should do nothing if given empty value', function () { - fileWriter.write(oldPath, '', callbackSpy) - expect(fs.renameSync).not.toHaveBeenCalled() - expect(fs.utimesSync).not.toHaveBeenCalled() - expect(callbackSpy).toHaveBeenCalledWith(null, null) - }) + it("should do nothing if given empty value", function() { + fileWriter.write(oldPath, "", callbackSpy); + expect(fs.renameSync).not.toHaveBeenCalled(); + expect(fs.utimesSync).not.toHaveBeenCalled(); + expect(callbackSpy).toHaveBeenCalledWith(null, null); + }); - it('should not allow path separator', function () { - const str = Path.join('only', 'use', 'this-last-piece.txt') - fileWriter.write(oldPath, str, callbackSpy) - expect(fs.renameSync).toHaveBeenCalledWith(oldPath, '/path/to/notes/this-last-piece.txt') - expect(fs.utimesSync).toHaveBeenCalled() - expect(callbackSpy).toHaveBeenCalledWith(null, null) - }) + it("should not allow path separator", function() { + const str = Path.join("only", "use", "this-last-piece.txt"); + fileWriter.write(oldPath, str, callbackSpy); + expect(fs.renameSync).toHaveBeenCalledWith( + oldPath, + "/path/to/notes/this-last-piece.txt" + ); + expect(fs.utimesSync).toHaveBeenCalled(); + expect(callbackSpy).toHaveBeenCalledWith(null, null); + }); - it('should trim and normalize value', function () { - fileWriter.write(oldPath, ' test.txt ', callbackSpy) - expect(fs.renameSync).toHaveBeenCalledWith(oldPath, '/path/to/notes/test.txt') - expect(fs.utimesSync).toHaveBeenCalled() - expect(callbackSpy).toHaveBeenCalledWith(null, null) - }) - }) - }) - }) -}) + it("should trim and normalize value", function() { + fileWriter.write(oldPath, " test.txt ", callbackSpy); + expect(fs.renameSync).toHaveBeenCalledWith( + oldPath, + "/path/to/notes/test.txt" + ); + expect(fs.utimesSync).toHaveBeenCalled(); + expect(callbackSpy).toHaveBeenCalledWith(null, null); + }); + }); + }); + }); +});