diff --git a/.travis.yml b/.travis.yml
index 44b3916..85ab36f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -24,10 +24,10 @@ before_install:
install:
- npm install ember-cli-sass # NOTE: this requies scripts
- npm install --ignore-scripts
-- bower install
+- ./node_modules/.bin/bower install
script:
-- npm run lint -s
-- ember try:one $EMBER_TRY_SCENARIO --- COVERAGE=true ember test
+- npm run lint
+- npm run ci-test
after_success:
- .travis/publish-coverage.sh
env:
diff --git a/.travis/publish-coverage.sh b/.travis/publish-coverage.sh
old mode 100644
new mode 100755
diff --git a/.travis/publish-gh-pages.sh b/.travis/publish-gh-pages.sh
old mode 100644
new mode 100755
diff --git a/addon/components/frost-fixed-table.js b/addon/components/frost-fixed-table.js
new file mode 100644
index 0000000..5315925
--- /dev/null
+++ b/addon/components/frost-fixed-table.js
@@ -0,0 +1,334 @@
+/**
+ * Component definition for the frost-fixed-table component
+ */
+import Ember from 'ember'
+const {$} = Ember
+import computed, {readOnly} from 'ember-computed-decorators'
+import {Component} from 'ember-frost-core'
+import {PropTypes} from 'ember-prop-types'
+
+import {ColumnPropType, ItemsPropType} from 'ember-frost-table/typedefs'
+import layout from '../templates/components/frost-fixed-table'
+
+export default Component.extend({
+ // == Dependencies ==========================================================
+
+ // == Keyword Properties ====================================================
+
+ layout,
+
+ // == PropTypes =============================================================
+
+ propTypes: {
+ // options
+ columns: PropTypes.arrayOf(ColumnPropType),
+ items: ItemsPropType
+ },
+
+ getDefaultProps () {
+ return {
+ // options
+ columns: [],
+ items: []
+ }
+ },
+
+ // == Computed Properties ===================================================
+
+ @readOnly
+ @computed('css')
+ /**
+ * The selector for the left body DOM element (specifically the scroll wrapper)
+ * @param {String} css - the base css class name for the component
+ * @returns {String} a sutiable jQuery selector for the left section of the table body
+ */
+ bodyLeftSelector (css) {
+ return `.${css}-left .frost-scroll`
+ },
+
+ @readOnly
+ @computed('css')
+ /**
+ * The selector for the middle body DOM element (specifically the scroll wrapper)
+ * @param {String} css - the base css class name for the component
+ * @returns {String} a sutiable jQuery selector for the middle section of the table body
+ */
+ bodyMiddleSelector (css) {
+ return `.${css}-middle .frost-scroll`
+ },
+
+ @readOnly
+ @computed('css')
+ /**
+ * The selector for the right body DOM element (specifically the scroll wrapper)
+ * @param {String} css - the base css class name for the component
+ * @returns {String} a sutiable jQuery selector for the right section of the table body
+ */
+ bodyRightSelector (css) {
+ return `.${css}-right .frost-scroll`
+ },
+
+ @readOnly
+ @computed('css')
+ /**
+ * The selector for the middle header DOM element (specifically the scroll wrapper)
+ * @param {String} css - the base css class name for the component
+ * @returns {String} a sutiable jQuery selector for the middle section of the table header
+ */
+ headerMiddleSelector (css) {
+ return `.${css}-header-middle .frost-scroll`
+ },
+
+ @readOnly
+ @computed('columns')
+ /**
+ * Get the set of columns that are supposed to be frozen on the left
+ *
+ * The set of leftColumns is defined as all the columns with `frozen` === `true`
+ * starting with the first column until we reach one w/o `frozen` === `true`
+ *
+ * @param {Column[]} columns - all the columns
+ * @returns {Column[]} just the left-most frozen columns
+ */
+ leftColumns (columns) {
+ const frozenColumns = []
+ for (let i = 0; i < columns.length; i++) {
+ const column = columns[i]
+ if (column.frozen) {
+ frozenColumns.push(column)
+ } else {
+ return frozenColumns
+ }
+ }
+
+ return frozenColumns
+ },
+
+ @readOnly
+ @computed('columns')
+ /**
+ * Get the set of columns that are supposed to be in the middle (between the frozen left and frozen right columns)
+ *
+ * The set of middleColumns is defined as all the columns with `frozen` === `false`
+ * starting with whatever the first column is with `frozen` === `false` until we reach one with `frozen` === `true`
+ *
+ * @param {Column[]} columns - all the columns
+ * @returns {Column[]} just the middle columns
+ */
+ middleColumns (columns) {
+ const unFrozenColumns = []
+ let foundUnFrozen = false
+ for (let i = 0; i < columns.length; i++) {
+ const column = columns[i]
+ if (column.frozen) {
+ if (foundUnFrozen) {
+ return unFrozenColumns
+ }
+ } else {
+ foundUnFrozen = true
+ unFrozenColumns.push(column)
+ }
+ }
+
+ return unFrozenColumns
+ },
+
+ @readOnly
+ @computed('columns')
+ /**
+ * Get the set of columns that are supposed to be frozen on the right
+ *
+ * The set of rightColumns is defined as all the columns with `frozen` === `true`
+ * starting with the first `frozen` === `true` column after we've seen at least one `frozen` === `false` column.
+ *
+ * @param {Column[]} columns - all the columns
+ * @returns {Column[]} just the middle columns
+ */
+ rightColumns (columns) {
+ const frozenColumns = []
+ for (let i = columns.length - 1; i > 0; i--) {
+ const column = columns[i]
+ if (column.frozen) {
+ frozenColumns.push(column)
+ } else {
+ return frozenColumns.reverse()
+ }
+ }
+
+ return frozenColumns.reverse()
+ },
+
+ // == Functions =============================================================
+
+ /**
+ * Get the width of the middle section by adding up the widths of all the cells
+ * @param {String} cellSelector - the selector to use to find the cells
+ * @returns {Number} the combined outer width of all cells (in pixels)
+ */
+ _calculateWidth (cellSelector) {
+ let width = 0
+
+ this.$(cellSelector).toArray().forEach((el) => {
+ width += $(el).outerWidth()
+ })
+
+ return width
+ },
+
+ /**
+ * Make the three body sections (left, middle, right) the correct height to stay within the bounds of the
+ * table itself
+ */
+ setupBodyHeights () {
+ const headerMiddleSelector = this.get('headerMiddleSelector')
+ const headerHeight = this.$(headerMiddleSelector).outerHeight()
+ const tableHeight = this.$().outerHeight()
+ const bodyHeight = tableHeight - headerHeight
+
+ const bodyLeftSelector = this.get('bodyLeftSelector')
+ const bodyMiddleSelector = this.get('bodyMiddleSelector')
+ const bodyRightSelector = this.get('bodyRightSelector')
+
+ ;[bodyLeftSelector, bodyMiddleSelector, bodyRightSelector].forEach((selector) => {
+ this.$(selector).css({height: `${bodyHeight}px`})
+ })
+ },
+
+ /**
+ * frost-scroll seems to display scroll bars on hover, sooo, we need to proxy hover events to the place where
+ * the scrollbar is present, the middle body when the middle of the header is hovered, and the right body when
+ * the left or middle body is hovered.
+ */
+ setupHoverProxy () {
+ const hoverClass = 'ps-container-hover'
+ const bodyLeftSelector = this.get('bodyLeftSelector')
+ const bodyMiddleSelector = this.get('bodyMiddleSelector')
+ const bodyRightSelector = this.get('bodyRightSelector')
+
+ ;[bodyLeftSelector, bodyMiddleSelector].forEach((selector) => {
+ const $element = this.$(selector)
+ $element.on('mouseenter', () => {
+ this.$(bodyRightSelector).addClass(hoverClass)
+ })
+ $element.on('mouseleave', () => {
+ this.$(bodyRightSelector).removeClass(hoverClass)
+ })
+ })
+
+ const headerMiddleSelector = this.get('headerMiddleSelector')
+ const $headerMiddle = this.$(headerMiddleSelector)
+ $headerMiddle.on('mouseenter', () => {
+ this.$(bodyMiddleSelector).addClass(hoverClass)
+ })
+
+ $headerMiddle.on('mouseleave', () => {
+ this.$(bodyMiddleSelector).removeClass(hoverClass)
+ })
+ },
+
+ /**
+ * Calculate the widths of the left and right side and set the marings of the middle accordingly.
+ */
+ setupMiddleMargins () {
+ const bodyLeftSelector = this.get('bodyLeftSelector')
+ const bodyRightSelector = this.get('bodyRightSelector')
+
+ const leftWidth = this.$(bodyLeftSelector).outerWidth()
+ const rightWidth = this.$(bodyRightSelector).outerWidth()
+
+ const headerMiddleSelector = this.get('headerMiddleSelector')
+ const bodyMiddleSelector = this.get('bodyMiddleSelector')
+ ;[headerMiddleSelector, bodyMiddleSelector].forEach((selector) => {
+ this.$(selector).css({
+ 'margin-left': `${leftWidth}px`,
+ 'margin-right': `${rightWidth}px`
+ })
+ })
+ },
+
+ /**
+ * Calculate how wide the middle sections should be by adding the sum of all the inner cells, then set that width
+ */
+ setupMiddleWidths () {
+ const headerMiddleSelector = this.get('headerMiddleSelector')
+ const bodyMiddleSelector = this.get('bodyMiddleSelector')
+
+ const width = this._calculateWidth(`${headerMiddleSelector} .frost-table-cell`)
+ this.$(`${headerMiddleSelector} .frost-table-header`).css({width: `${width}px`})
+ this.$(`${bodyMiddleSelector} .frost-table-row`).css({width: `${width}px`})
+ },
+
+ /**
+ * Set up the scroll synchronization between the different components within the table that should scroll together
+ */
+ setupScrollSync () {
+ const headerMiddleSelector = this.get('headerMiddleSelector')
+ const bodyLeftSelector = this.get('bodyLeftSelector')
+ const bodyMiddleSelector = this.get('bodyMiddleSelector')
+ const bodyRightSelector = this.get('bodyRightSelector')
+
+ this.syncScrollLeft(headerMiddleSelector, bodyMiddleSelector)
+ this.syncScrollLeft(bodyMiddleSelector, headerMiddleSelector)
+
+ this.syncScrollTop(bodyLeftSelector, bodyMiddleSelector, bodyRightSelector)
+ this.syncScrollTop(bodyMiddleSelector, bodyLeftSelector, bodyRightSelector)
+ this.syncScrollTop(bodyRightSelector, bodyLeftSelector, bodyMiddleSelector)
+ },
+
+ /**
+ * Sync horizontal scrolling between a source of scroll events and a set of destination selectors
+ * @param {String} source - the selector of the source of the scroll events
+ * @param {String[]} destinations - the selectors of the destination (the ones being driven by the source)
+ */
+ syncScrollLeft (source, ...destinations) {
+ this.$(source).on('scroll', () => {
+ // NOTE: intentionally not putting this in the ember run loop because doing so made it much less responsive
+ // there was a noticible lag between scrolling and re-positioning the synced element. Plus it's not updating
+ // any DOM content, just setting scroll positions.
+ const $source = this.$(source)
+ destinations.forEach((destination) => {
+ const $destination = this.$(destination)
+ $destination.scrollLeft($source.scrollLeft())
+ })
+ })
+ },
+
+ /**
+ * Sync vertical scrolling between a source of scroll events and a set of destination selectors
+ * @param {String} source - the selector of the source of the scroll events
+ * @param {String[]} destinations - the selectors of the destination (the ones being driven by the source)
+ */
+ syncScrollTop (source, ...destinations) {
+ this.$(source).on('scroll', () => {
+ // NOTE: intentionally not putting this in the ember run loop because doing so made it much less responsive
+ // there was a noticible lag between scrolling and re-positioning the synced element. Plus it's not updating
+ // any DOM content, just setting scroll positions.
+ const $source = this.$(source)
+ destinations.forEach((destination) => {
+ const $destination = this.$(destination)
+ $destination.scrollTop($source.scrollTop())
+ })
+ })
+ },
+
+ // == DOM Events ============================================================
+
+ // == Lifecycle Hooks =======================================================
+
+ /**
+ * Set up synced scrolling as well as calculating padding for middle sections
+ */
+ didRender () {
+ this._super(...arguments)
+ this.setupBodyHeights()
+ this.setupHoverProxy()
+ this.setupMiddleWidths()
+ this.setupMiddleMargins()
+ this.setupScrollSync()
+ },
+
+ // == Actions ===============================================================
+
+ actions: {
+ }
+})
diff --git a/addon/components/frost-table-body.js b/addon/components/frost-table-body.js
index 4100526..30e843d 100644
--- a/addon/components/frost-table-body.js
+++ b/addon/components/frost-table-body.js
@@ -2,51 +2,34 @@
* Component definition for the frost-table-body component
*/
-import Ember from 'ember'
-const {Component} = Ember
-import PropTypesMixin, {PropTypes} from 'ember-prop-types'
+import {Component} from 'ember-frost-core'
+import {PropTypes} from 'ember-prop-types'
+import {ColumnPropType, ItemsPropType} from 'ember-frost-table/typedefs'
import layout from '../templates/components/frost-table-body'
-export default Component.extend(PropTypesMixin, {
+export default Component.extend({
// == Dependencies ==========================================================
// == Keyword Properties ====================================================
- classNameBindings: ['css'],
layout,
tagName: 'tbody',
// == PropTypes =============================================================
- /**
- * Properties for this component. Options are expected to be (potentially)
- * passed in to the component. State properties are *not* expected to be
- * passed in/overwritten.
- */
propTypes: {
// options
- columns: PropTypes.arrayOf(PropTypes.shape({
- className: PropTypes.string,
- label: PropTypes.string,
- propertyName: PropTypes.string.isRequired
- })),
- css: PropTypes.string,
- hook: PropTypes.string.isRequired,
- items: PropTypes.array,
+ columns: PropTypes.arrayOf(ColumnPropType),
+ items: ItemsPropType
// state
-
- // keywords
- layout: PropTypes.any
},
- /** @returns {Object} the default property values when not provided by consumer */
getDefaultProps () {
return {
// options
columns: [],
- css: this.getCss(),
items: []
// state
@@ -57,13 +40,6 @@ export default Component.extend(PropTypesMixin, {
// == Functions =============================================================
- /**
- * @returns {String} the base css class for this component (the component name)
- */
- getCss () {
- return this.toString().replace(/^.+:(.+)::.+$/, '$1')
- },
-
// == DOM Events ============================================================
// == Lifecycle Hooks =======================================================
diff --git a/addon/components/frost-table-cell.js b/addon/components/frost-table-cell.js
new file mode 100644
index 0000000..6a626ea
--- /dev/null
+++ b/addon/components/frost-table-cell.js
@@ -0,0 +1,29 @@
+/**
+ * Component definition for the frost-table-cell component
+ */
+
+import {Component} from 'ember-frost-core'
+
+export default Component.extend({
+ // == Dependencies ==========================================================
+
+ // == Keyword Properties ====================================================
+
+ attributeBindings: ['title'],
+ tagName: 'td',
+
+ // == PropTypes =============================================================
+
+ // == Computed Properties ===================================================
+
+ // == Functions =============================================================
+
+ // == DOM Events ============================================================
+
+ // == Lifecycle Hooks =======================================================
+
+ // == Actions ===============================================================
+
+ actions: {
+ }
+})
diff --git a/addon/components/frost-table-header.js b/addon/components/frost-table-header.js
index bd1f5ea..e65c6b6 100644
--- a/addon/components/frost-table-header.js
+++ b/addon/components/frost-table-header.js
@@ -2,52 +2,37 @@
* Component definition for the frost-table-header component
*/
-import Ember from 'ember'
-const {Component} = Ember
-import PropTypesMixin, {PropTypes} from 'ember-prop-types'
+import {Component} from 'ember-frost-core'
+import {PropTypes} from 'ember-prop-types'
+import {ColumnPropType} from 'ember-frost-table/typedefs'
import layout from '../templates/components/frost-table-header'
-export default Component.extend(PropTypesMixin, {
+export default Component.extend({
// == Dependencies ==========================================================
// == Keyword Properties ====================================================
- classNameBindings: ['css'],
layout,
tagName: 'thead',
// == PropTypes =============================================================
- /**
- * Properties for this component. Options are expected to be (potentially)
- * passed in to the component. State properties are *not* expected to be
- * passed in/overwritten.
- */
propTypes: {
// options
- columns: PropTypes.arrayOf(PropTypes.shape({
- className: PropTypes.string,
- label: PropTypes.string,
- propertyName: PropTypes.string.isRequired
- })),
- css: PropTypes.string,
- hook: PropTypes.string.isRequired,
+ cellCss: PropTypes.string,
+ cellTagName: PropTypes.string,
+ columns: PropTypes.arrayOf(ColumnPropType)
// state
-
- // keywords
- classNameBindings: PropTypes.arrayOf(PropTypes.string),
- layout: PropTypes.any,
- tagName: PropTypes.any
},
- /** @returns {Object} the default property values when not provided by consumer */
getDefaultProps () {
return {
// options
- columns: [],
- css: this.getCss()
+ cellCss: this.get('css'),
+ cellTagName: 'th',
+ columns: []
// state
}
@@ -57,13 +42,6 @@ export default Component.extend(PropTypesMixin, {
// == Functions =============================================================
- /**
- * @returns {String} the base css class for this component (the component name)
- */
- getCss () {
- return this.toString().replace(/^.+:(.+)::.+$/, '$1')
- },
-
// == DOM Events ============================================================
// == Lifecycle Hooks =======================================================
diff --git a/addon/components/frost-table-row.js b/addon/components/frost-table-row.js
index b0b854e..ef36f86 100644
--- a/addon/components/frost-table-row.js
+++ b/addon/components/frost-table-row.js
@@ -2,55 +2,38 @@
* Component definition for the frost-table-body-row component
*/
-import Ember from 'ember'
-const {Component} = Ember
-import PropTypesMixin, {PropTypes} from 'ember-prop-types'
+import {Component} from 'ember-frost-core'
+import {PropTypes} from 'ember-prop-types'
+import {ColumnPropType} from 'ember-frost-table/typedefs'
import layout from '../templates/components/frost-table-row'
-export default Component.extend(PropTypesMixin, {
+export default Component.extend({
// == Dependencies ==========================================================
// == Keyword Properties ====================================================
- classNameBindings: ['css'],
layout,
tagName: 'tr',
// == PropTypes =============================================================
- /**
- * Properties for this component. Options are expected to be (potentially)
- * passed in to the component. State properties are *not* expected to be
- * passed in/overwritten.
- */
propTypes: {
// options
- columns: PropTypes.arrayOf(PropTypes.shape({
- className: PropTypes.string,
- label: PropTypes.string,
- propertyName: PropTypes.string.isRequired
- })),
cellCss: PropTypes.string,
- css: PropTypes.string,
- hook: PropTypes.string.isRequired,
- item: PropTypes.object,
+ cellTagName: PropTypes.string,
+ columns: PropTypes.arrayOf(ColumnPropType),
+ item: PropTypes.object
// state
-
- // keywords
- classNameBindings: PropTypes.arrayOf(PropTypes.string),
- layout: PropTypes.any,
- tagName: PropTypes.string
},
- /** @returns {Object} the default property values when not provided by consumer */
getDefaultProps () {
return {
// options
+ cellTagName: 'td',
+ cellCss: this.get('css'),
columns: [],
- cellCss: this.getCss(),
- css: this.getCss(),
item: {}
// state
@@ -61,13 +44,6 @@ export default Component.extend(PropTypesMixin, {
// == Functions =============================================================
- /**
- * @returns {String} the base css class for this component (the component name)
- */
- getCss () {
- return this.toString().replace(/^.+:(.+)::.+$/, '$1')
- },
-
// == DOM Events ============================================================
// == Lifecycle Hooks =======================================================
diff --git a/addon/components/frost-table.js b/addon/components/frost-table.js
index 42023f8..ba05ecf 100644
--- a/addon/components/frost-table.js
+++ b/addon/components/frost-table.js
@@ -2,53 +2,34 @@
* Component definition for the frost-table component
*/
-import Ember from 'ember'
-const {Component} = Ember
-import PropTypesMixin, {PropTypes} from 'ember-prop-types'
+import {Component} from 'ember-frost-core'
+import {PropTypes} from 'ember-prop-types'
+import {ColumnPropType} from 'ember-frost-table/typedefs'
import layout from '../templates/components/frost-table'
-export default Component.extend(PropTypesMixin, {
+export default Component.extend({
// == Dependencies ==========================================================
// == Keyword Properties ====================================================
- classNameBindings: ['css'],
layout,
tagName: 'table',
// == PropTypes =============================================================
- /**
- * Properties for this component. Options are expected to be (potentially)
- * passed in to the component. State properties are *not* expected to be
- * passed in/overwritten.
- */
propTypes: {
// options
- columns: PropTypes.arrayOf(PropTypes.shape({
- className: PropTypes.string,
- label: PropTypes.string,
- propertyName: PropTypes.string.isRequired
- })),
- css: PropTypes.string,
- hook: PropTypes.string.isRequired,
- items: PropTypes.array,
+ columns: PropTypes.arrayOf(ColumnPropType),
+ items: PropTypes.array
// state
-
- // keywords
- classNameBindings: PropTypes.arrayOf(PropTypes.string),
- layout: PropTypes.any,
- tagName: PropTypes.any
},
- /** @returns {Object} the default property values when not provided by consumer */
getDefaultProps () {
return {
// options
columns: [],
- css: this.getCss(),
items: []
// state
@@ -59,13 +40,6 @@ export default Component.extend(PropTypesMixin, {
// == Functions =============================================================
- /**
- * @returns {String} the base css class for this component (the component name)
- */
- getCss () {
- return this.toString().replace(/^.+:(.+)::.+$/, '$1')
- },
-
// == DOM Events ============================================================
// == Lifecycle Hooks =======================================================
diff --git a/addon/helpers/extend.js b/addon/helpers/extend.js
deleted file mode 100644
index ddc47e0..0000000
--- a/addon/helpers/extend.js
+++ /dev/null
@@ -1,13 +0,0 @@
-/**
- * The extend helper, used to extend a given object by adding properites from another object to it
- * the original object is not modified, but rather the properties of all objects are copied onto a new, empty object
- */
-import Ember from 'ember'
-const {Helper, assign} = Ember
-const {helper} = Helper
-
-export function extend ([original], newProps) {
- return assign({}, original, newProps)
-}
-
-export default helper(extend)
diff --git a/addon/templates/components/frost-fixed-table.hbs b/addon/templates/components/frost-fixed-table.hbs
new file mode 100644
index 0000000..ff14741
--- /dev/null
+++ b/addon/templates/components/frost-fixed-table.hbs
@@ -0,0 +1,93 @@
+{{! Template for the frost-fixed-table component }}
+
+ {{!--
+ Strange as it seems, we want middle above left/right so that left/right are on top of middle.
+ The left and right are positioned absolutely so that they stay where they should be
+ --}}
+
+ {{#frost-scroll}}
+ {{frost-table-header
+ cellTagName='div'
+ columns=middleColumns
+ hook=(concat hookPrefix '-header-middle')
+ hookQualifiers=hookQualifiers
+ tagName='div'
+ }}
+ {{/frost-scroll}}
+
+
+ {{frost-table-header
+ cellTagName='div'
+ columns=leftColumns
+ hook=(concat hookPrefix '-header-left')
+ hookQualifiers=hookQualifiers
+ tagName='div'
+ }}
+
+
+ {{frost-table-header
+ cellTagName='div'
+ columns=rightColumns
+ hook=(concat hookPrefix '-header-right')
+ hookQualifiers=hookQualifiers
+ tagName='div'
+ }}
+
+
+
+
+ {{!--
+ Strange as it seems, we want middle above left/right so that left/right are on top of middle.
+ The left and right are positioned absolutely so that they stay where they should be
+ --}}
+
+ {{#frost-scroll}}
+ {{#each items as |item index|}}
+ {{frost-table-row
+ cellTagName='div'
+ columns=middleColumns
+ hook=(concat hookPrefix '-middle')
+ hookQualifiers=(extend hookQualifiers row=index)
+ item=item
+ tagName='div'
+ }}
+ {{/each}}
+ {{/frost-scroll}}
+
+
+
+ {{#frost-scroll}}
+ {{#each items as |item index|}}
+ {{frost-table-row
+ cellTagName='div'
+ columns=leftColumns
+ hook=(concat hookPrefix '-left')
+ hookQualifiers=(extend hookQualifiers row=index)
+ item=item
+ tagName='div'
+ }}
+ {{/each}}
+ {{/frost-scroll}}
+
+
+
+ {{#frost-scroll}}
+ {{#each items as |item index|}}
+ {{frost-table-row
+ cellTagName='div'
+ columns=rightColumns
+ hook=(concat hookPrefix '-right')
+ hookQualifiers=(extend hookQualifiers row=index)
+ item=item
+ tagName='div'
+ }}
+ {{/each}}
+ {{/frost-scroll}}
+
+
diff --git a/addon/templates/components/frost-table-body.hbs b/addon/templates/components/frost-table-body.hbs
index 29d48b9..f49eba7 100644
--- a/addon/templates/components/frost-table-body.hbs
+++ b/addon/templates/components/frost-table-body.hbs
@@ -3,7 +3,7 @@
{{frost-table-row
cellCss=css
columns=columns
- hook=(concat hook '-row')
+ hook=(concat hookPrefix '-row')
hookQualifiers=(extend hookQualifiers row=index)
item=item
}}
diff --git a/addon/templates/components/frost-table-header.hbs b/addon/templates/components/frost-table-header.hbs
index 1055e04..56f9f50 100644
--- a/addon/templates/components/frost-table-header.hbs
+++ b/addon/templates/components/frost-table-header.hbs
@@ -1,9 +1,11 @@
{{! Template for the frost-table-header component }}
-{{#each columns as |column|}}
-
+{{#each columns as |column index|}}
+ {{#frost-table-cell
+ tagName=cellTagName
+ class=(concat cellCss '-cell' ' ' column.className)
+ hook=(concat hookPrefix '-cell')
+ hookQualifiers=(extend hookQualifiers column=index)
+ }}
{{~ column.label ~}}
- |
+ {{/frost-table-cell}}
{{/each}}
diff --git a/addon/templates/components/frost-table-row.hbs b/addon/templates/components/frost-table-row.hbs
index ba58998..a54ddf7 100644
--- a/addon/templates/components/frost-table-row.hbs
+++ b/addon/templates/components/frost-table-row.hbs
@@ -1,9 +1,11 @@
{{! Template for the frost-table-row component }}
{{#each columns as |column index|}}
-
+ {{#frost-table-cell
+ tagName=cellTagName
+ class=(concat cellCss '-cell' ' ' column.className)
+ hook=(concat hookPrefix '-cell')
+ hookQualifiers=(extend hookQualifiers column=index)
+ }}
{{~ get item column.propertyName ~}}
- |
+ {{/frost-table-cell}}
{{/each}}
diff --git a/addon/typedefs.js b/addon/typedefs.js
index 8d579b8..7f219bd 100644
--- a/addon/typedefs.js
+++ b/addon/typedefs.js
@@ -2,9 +2,31 @@
* Type definitions for ember-frost-table
*/
+import {PropTypes} from 'ember-prop-types'
+
/**
* @typedef Column
* @property {String} [className] - the name of the class to add to all cells of a column
* @property {String} label - the column header label
* @property {String} propertyName - the name of the property in the data record to display in this column
+ * @property {Boolean} [frozen=false] - true if this column should be frozen (on either the left or right side of the table)
+ * @property {Component} [renderer] - the cell renderer to use for all data cells in this column
*/
+
+export const ColumnPropType = PropTypes.shape({
+ className: PropTypes.string,
+ frozen: PropTypes.bool,
+ label: PropTypes.string,
+ propertyName: PropTypes.string.isRequired,
+ renderer: PropTypes.any
+})
+
+export const ItemsPropType = PropTypes.oneOfType([
+ PropTypes.EmberObject, // DS.RecordArray
+ PropTypes.arrayOf(
+ PropTypes.oneOfType([
+ PropTypes.object,
+ PropTypes.EmberObject
+ ])
+ )
+])
diff --git a/app/components/frost-fixed-table.js b/app/components/frost-fixed-table.js
new file mode 100644
index 0000000..752694d
--- /dev/null
+++ b/app/components/frost-fixed-table.js
@@ -0,0 +1,4 @@
+/**
+ * Simple re-export of frost-fixed-table in the app namespace
+ */
+export {default} from 'ember-frost-table/components/frost-fixed-table'
diff --git a/app/components/frost-table-cell.js b/app/components/frost-table-cell.js
new file mode 100644
index 0000000..b6e52af
--- /dev/null
+++ b/app/components/frost-table-cell.js
@@ -0,0 +1,4 @@
+/**
+ * Simple re-export of frost-table-cell in the app namespace
+ */
+export {default} from 'ember-frost-table/components/frost-table-cell'
diff --git a/app/helpers/ehook.js b/app/helpers/ehook.js
deleted file mode 100644
index c51c1a9..0000000
--- a/app/helpers/ehook.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default, ehook } from 'ember-frost-table/helpers/ehook'
diff --git a/app/helpers/extend.js b/app/helpers/extend.js
deleted file mode 100644
index 31ef5ff..0000000
--- a/app/helpers/extend.js
+++ /dev/null
@@ -1 +0,0 @@
-export { default, extend } from 'ember-frost-table/helpers/extend'
diff --git a/app/styles/_frost-fixed-table.scss b/app/styles/_frost-fixed-table.scss
new file mode 100644
index 0000000..7ef36fc
--- /dev/null
+++ b/app/styles/_frost-fixed-table.scss
@@ -0,0 +1,72 @@
+//
+// Styles specific for the frost-fixed-table component (and its pieces)
+//
+
+.frost-fixed-table {
+ position: relative;
+
+ &-header,
+ &-body {
+ display: flex;
+ flex-direction: row;
+ }
+
+ &-header {
+ &-left {
+ position: absolute;
+ left: 0;
+ }
+
+ &-middle {
+ position: relative;
+ overflow: hidden;
+
+ // NOTE this level of specificity is required to override perfect-scroll default styles
+ .ps-container > .ps-scrollbar-x-rail {
+ display: none;
+ }
+ }
+
+ &-right {
+ position: absolute;
+ right: 0;
+ }
+ }
+
+ &-left,
+ &-middle,
+ &-right {
+ overflow: hidden;
+ }
+
+ &-left {
+ position: absolute;
+ left: 0;
+ box-shadow: 23px 0 15px -4px $frost-table-box-shadow-color;
+
+ // NOTE this level of specificity is required to override perfect-scroll default styles
+ .ps-container > .ps-scrollbar-y-rail {
+ display: none;
+ }
+ }
+
+ &-middle {
+ position: relative;
+
+ // NOTE this level of specificity is required to override perfect-scroll default styles
+ .ps-container > .ps-scrollbar-y-rail {
+ display: none;
+ }
+ }
+
+ &-right {
+ position: absolute;
+ right: 0;
+ box-shadow: -23px 0 15px -4px $frost-table-box-shadow-color;
+ }
+
+ .frost-table-cell {
+ @include frost-table-truncate-text;
+ display: inline-block;
+ }
+}
diff --git a/app/styles/_frost-table.scss b/app/styles/_frost-table.scss
new file mode 100644
index 0000000..1106c42
--- /dev/null
+++ b/app/styles/_frost-table.scss
@@ -0,0 +1,51 @@
+//
+// Styles specific for the frost-table component (and its pieces)
+//
+
+.frost-table {
+ table-layout: fixed;
+
+ &-cell {
+ padding: $frost-table-cell-padding;
+ border-right: solid 1px $frost-color-lgrey-1;
+ text-align: left;
+
+ &:last-child {
+ border-right: 0;
+ }
+
+ &.right {
+ text-align: right;
+ }
+ }
+
+ &-header {
+ background-color: $frost-color-white;
+ color: $frost-color-grey-4;
+ font-weight: bold;
+
+ &-cell {
+ min-width: 40px;
+ height: $frost-table-header-height;
+ }
+ }
+
+ &-row {
+ &:nth-child(even) {
+ background-color: $frost-color-white;
+ }
+
+ &:nth-child(odd) {
+ background-color: $frost-color-lgrey-3;
+ }
+
+ // NOTE: keep the .even and .odd below the :nth-child() rules so they can override the default nth-row behavior
+ &.even {
+ background-color: $frost-color-white;
+ }
+
+ &.odd {
+ background-color: $frost-color-lgrey-3;
+ }
+ }
+}
diff --git a/app/styles/_mixins.scss b/app/styles/_mixins.scss
new file mode 100644
index 0000000..f8bb48b
--- /dev/null
+++ b/app/styles/_mixins.scss
@@ -0,0 +1,9 @@
+//
+// Mixins used by the styles for the ember-frost-table components
+//
+
+@mixin frost-table-truncate-text {
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ overflow: hidden;
+}
diff --git a/app/styles/_perfect-scrollbar.scss b/app/styles/_perfect-scrollbar.scss
new file mode 100644
index 0000000..088d30b
--- /dev/null
+++ b/app/styles/_perfect-scrollbar.scss
@@ -0,0 +1,15 @@
+//
+// Some overrides we need for perfect-scrollbar, specifically, we need to be able to apply the :hover styles when
+// the container isn't actually hovered, but rather, when a class name is added.
+// By necesity, these are gonna be fragile, and may need to be tweaked when we update perfect-scrollbar
+//
+
+.ps-container-hover {
+ .ps-scrollbar-y-rail {
+ opacity: .6;
+ }
+
+ .ps-scrollbar-x-rail {
+ opacity: .6;
+ }
+}
diff --git a/app/styles/_variables.scss b/app/styles/_variables.scss
index e15c9aa..fdf6de5 100644
--- a/app/styles/_variables.scss
+++ b/app/styles/_variables.scss
@@ -6,5 +6,4 @@ $frost-table-header-height: 40px;
$frost-table-cell-padding: 10px;
$frost-table-border-width: 1px;
-$frost-table-scrollable-body-box-shadow-color: rgba(0, 0, 0, .13);
-$frost-table-scrollable-body-box-shadow: 0 1px 15px 3px $frost-table-scrollable-body-box-shadow-color;
+$frost-table-box-shadow-color: rgba(0, 0, 0, .08);
diff --git a/app/styles/ember-frost-table.scss b/app/styles/ember-frost-table.scss
index 8e84692..6b67cc8 100644
--- a/app/styles/ember-frost-table.scss
+++ b/app/styles/ember-frost-table.scss
@@ -1,61 +1,9 @@
//
-// Styles for a frost-table component
+// Styles for a ember-frost-table addon
//
@import 'variables';
-
-// Common cell styles
-@mixin table-cell {
- padding: $frost-table-cell-padding;
- border-right: solid 1px $frost-color-lgrey-1;
- text-align: left;
-
- &:last-child {
- border-right: 0;
- }
-
- &.right {
- text-align: right;
- }
-}
-
-.frost-table {
- table-layout: fixed;
-
- &-header {
- background-color: $frost-color-white;
- color: $frost-color-grey-4;
- font-weight: bold;
-
- &-cell {
- @include table-cell;
- min-width: 40px;
- height: $frost-table-header-height;
- }
- }
-
- &-body {
- &-cell {
- @include table-cell;
- }
- }
-
- &-row {
- &:nth-child(even) {
- background-color: $frost-color-white;
- }
-
- &:nth-child(odd) {
- background-color: $frost-color-lgrey-3;
- }
-
- // NOTE: keep the .even and .odd below the :nth-child() rules so they can override the default nth-row behavior
- &.even {
- background-color: $frost-color-white;
- }
-
- &.odd {
- background-color: $frost-color-lgrey-3;
- }
- }
-}
+@import 'mixins';
+@import 'frost-table';
+@import 'frost-fixed-table';
+@import 'perfect-scrollbar';
diff --git a/bower.json b/bower.json
index 10c84c9..7fef768 100644
--- a/bower.json
+++ b/bower.json
@@ -1,10 +1,11 @@
{
"name": "ember-frost-table",
"dependencies": {
- "ember": "^2.8.0",
+ "ember": "~2.8.0",
"ember-cli-shims": "^0.1.3",
"ember-mocha-adapter": "~0.3.1",
"perfect-scrollbar": ">=0.6.7 <2.0.0",
- "chai-jquery": "^2.0.1"
+ "chai-jquery": "^2.0.1",
+ "sinon-chai": "^2.8.0"
}
}
diff --git a/config/ember-try.js b/config/ember-try.js
index 0f920ef..9cfad93 100644
--- a/config/ember-try.js
+++ b/config/ember-try.js
@@ -106,13 +106,13 @@ module.exports = {
}
},
{
- name: 'ember-2-7',
+ name: 'ember-2-8',
bower: {
dependencies: {
- 'ember': '~2.7.0'
+ 'ember': '~2.8.0'
},
resolutions: {
- 'ember': '~2.7.0'
+ 'ember': '~2.8.0'
}
}
},
diff --git a/ember-cli-build.js b/ember-cli-build.js
index 5ee4fad..1cb1098 100644
--- a/ember-cli-build.js
+++ b/ember-cli-build.js
@@ -24,7 +24,8 @@ module.exports = function (defaults) {
if (app.env === 'test') {
;[
- 'bower_components/chai-jquery/chai-jquery.js'
+ 'bower_components/chai-jquery/chai-jquery.js',
+ 'bower_components/sinon-chai/lib/sinon-chai.js'
].forEach((path) => {
app.import(path, {type: 'test'})
})
diff --git a/package.json b/package.json
index cebac47..16b6f95 100644
--- a/package.json
+++ b/package.json
@@ -2,54 +2,56 @@
"name": "ember-frost-table",
"version": "0.1.0",
"description": "A simple table component",
- "license": "MIT",
- "author": "Adam Meadows (https://github.com/job13er)",
- "contributors": [],
"directories": {
"doc": "doc",
"test": "tests"
},
- "repository": "git@github.com:ciena-frost/ember-frost-table.git",
"scripts": {
"build": "ember build",
"start": "ember server",
+ "ci-test": "ember try:one $EMBER_TRY_SCENARIO --- COVERAGE=true ember test",
"test": "npm run lint && COVERAGE=true ember test",
"eslint": "eslint *.js addon app blueprints config tests",
"md-lint": "find . -name '*.md' -depth 1 | grep -v CHANGELOG | xargs remark",
"sass-lint": "sass-lint -q -v",
"lint": "npm run eslint && npm run sass-lint && npm run md-lint"
},
+ "repository": "git@github.com:ciena-frost/ember-frost-table.git",
"engines": {
"node": ">= 6.0.0"
},
+ "author": "Adam Meadows (https://github.com/job13er)",
+ "contributors": [],
+ "license": "MIT",
"devDependencies": {
"broccoli-asset-rev": "^2.4.5",
- "ember-cli": "^2.8.0",
- "ember-cli-app-version": "^2.0.0",
+ "ember-cli": "~2.8.0",
+ "ember-cli-chai": "^0.2.0",
+ "ember-cli-code-coverage": "0.3.5",
"ember-cli-dependency-checker": "^1.3.0",
"ember-cli-htmlbars-inline-precompile": "^0.3.1",
"ember-cli-inject-live-reload": "^1.4.1",
- "ember-cli-mocha": "^0.11.0",
- "ember-cli-sri": "^2.1.0",
+ "ember-cli-mocha": "^0.13.0",
"ember-cli-template-lint": "^0.5.0",
"ember-cli-test-loader": "^1.1.0",
"ember-cli-uglify": "^1.2.0",
"ember-code-snippet": "1.8.0",
- "ember-computed-decorators": "^0.2.2",
- "ember-concurrency": "^0.7.15",
- "ember-data": "^2.8.0",
- "ember-disable-prototype-extensions": "^1.1.0",
- "ember-elsewhere": "^0.4.1",
+ "ember-computed-decorators": "~0.2.2",
+ "ember-concurrency": "~0.7.15",
+ "ember-data": "~2.8.0",
+ "ember-disable-proxy-controllers": "^1.0.1",
+ "ember-elsewhere": "~0.4.1",
"ember-export-application-global": "^1.0.5",
- "ember-frost-core": "^1.0.3",
+ "ember-frost-core": "^1.2.1",
"ember-get-config": "^0.1.11",
- "ember-hook": "job13er/ember-hook#make-hook-helper-extendible",
- "ember-load-initializers": "^0.5.1",
+ "ember-hook": "^1.3.5",
+ "ember-load-initializers": "^0.6.0",
"ember-lodash-shim": "1.0.1",
"ember-perfectscroll": "0.1.12",
+ "ember-prop-types": "^3.2.0",
"ember-resolver": "^2.0.3",
- "ember-sinon": "^0.5.1",
- "ember-test-utils": "^1.1.2",
+ "ember-sinon": "^0.6.0",
+ "ember-test-utils": "^1.3.1",
"ember-truth-helpers": "^1.2.0",
"eslint": "^3.4.0",
"eslint-config-frost-standard": "^5.0.0",
@@ -62,7 +64,7 @@
"dependencies": {
"ember-cli-babel": "^5.1.6",
"ember-cli-htmlbars": "^1.0.11",
- "ember-cli-sass": "^5.5.2"
+ "ember-cli-sass": "^5.6.0"
},
"keywords": [
"ember-addon",
@@ -71,4 +73,4 @@
"ember-addon": {
"configPath": "tests/dummy/config"
}
-}
\ No newline at end of file
+}
diff --git a/tests/dummy/app/pods/demo/frost-fixed-table/controller.js b/tests/dummy/app/pods/demo/frost-fixed-table/controller.js
new file mode 100644
index 0000000..4a2ce4f
--- /dev/null
+++ b/tests/dummy/app/pods/demo/frost-fixed-table/controller.js
@@ -0,0 +1,2 @@
+import HeroesController from '../heroes-controller'
+export default HeroesController.extend({})
diff --git a/tests/dummy/app/pods/demo/frost-fixed-table/template.hbs b/tests/dummy/app/pods/demo/frost-fixed-table/template.hbs
new file mode 100644
index 0000000..684f4ff
--- /dev/null
+++ b/tests/dummy/app/pods/demo/frost-fixed-table/template.hbs
@@ -0,0 +1,81 @@
+{{! template-lint-disable bare-strings }}
+{{!-- BEGIN-SNIPPET fixed-table-api }}
+ {{frost-fixed-table
+ columns=(array
+ (hash
+ className= // e.g. 'name-column'
+ label= // e.g. 'Name'
+ propertyName= // e.g. 'name'
+ )
+ )
+ hook= // e.g. 'myTable'
+ items=(array
+ (hash
+ name='Jane Doe'
+ )
+ (hash
+ name='John Doe'
+ )
+ )
+ }}
+{{ END-SNIPPET --}}
+
+
+
+
+ API
+
+
+ {{code-snippet name='fixed-table-api.hbs'}}
+
+
+
+
+ Live demo
+
+
+ {{code-snippet name='fixed-table.hbs'}}
+
+
+ {{! BEGIN-SNIPPET fixed-table }}
+ {{frost-fixed-table
+ columns=(array
+ (hash
+ className='name-col'
+ frozen=true
+ label='Name'
+ propertyName='name'
+ )
+ (hash
+ className='real-name-col'
+ label='Real Name'
+ propertyName='realName'
+ )
+ (hash
+ className='real-name-col'
+ label='Real Name'
+ propertyName='realName'
+ )
+ (hash
+ className='real-name-col'
+ label='Real Name'
+ propertyName='realName'
+ )
+ (hash
+ className='real-name-col'
+ label='Real Name'
+ propertyName='realName'
+ )
+ (hash
+ className='universe-col'
+ frozen=true
+ label='Universe'
+ propertyName='universe'
+ )
+ )
+ hook='myTable'
+ items=heroes
+ }}
+ {{! END-SNIPPET }}
+
+
diff --git a/tests/dummy/app/pods/demo/template.hbs b/tests/dummy/app/pods/demo/template.hbs
index 3b85e84..c184bd8 100644
--- a/tests/dummy/app/pods/demo/template.hbs
+++ b/tests/dummy/app/pods/demo/template.hbs
@@ -8,6 +8,7 @@
{{#link-to 'demo.overview'}}Overview{{/link-to}}
Components
+ {{#link-to 'demo.frost-fixed-table'}}frost-fixed-table{{/link-to}}
{{#link-to 'demo.frost-table'}}frost-table{{/link-to}}
{{#link-to 'demo.frost-table-header'}}frost-table-header{{/link-to}}
{{#link-to 'demo.frost-table-body'}}frost-table-body{{/link-to}}
diff --git a/tests/dummy/app/router.js b/tests/dummy/app/router.js
index f649b0a..f49d6b2 100644
--- a/tests/dummy/app/router.js
+++ b/tests/dummy/app/router.js
@@ -8,6 +8,7 @@ const Router = Ember.Router.extend({
Router.map(function () {
this.route('demo', { path: '/' }, function () {
this.route('overview', { path: '/' })
+ this.route('frost-fixed-table')
this.route('frost-table')
this.route('frost-table-body')
this.route('frost-table-header')
diff --git a/tests/dummy/app/styles/app.scss b/tests/dummy/app/styles/app.scss
index 4058b49..5ec4d95 100644
--- a/tests/dummy/app/styles/app.scss
+++ b/tests/dummy/app/styles/app.scss
@@ -155,31 +155,30 @@ dl {
height: 40px;
}
+.frost-table-demo {
+ padding: 10px ;
+}
+
+.frost-fixed-table-api {
+ max-width: 500px;
+}
// Table example styles
// BEGIN-SNIPPET hero-styles
-.frost-table-body-row-cell > span {
- height: 50px;
+.frost-fixed-table {
+ max-width: 500px;
+ max-height: 500px;
}
-.name-col > span {
- display: inline-block;
+.name-col {
width: 100px;
}
-.real-name-col > span {
- display: inline-block;
- width: 150px;
-}
-
-.universe-col > span {
- display: inline-block;
- width: 50px;
+.real-name-col {
+ width: 175px;
}
-.top,
-.bottom {
- width: 500px;
- overflow: auto;
+.universe-col {
+ width: 90px;
}
// END-SNIPPET
diff --git a/tests/dummy/config/environment.js b/tests/dummy/config/environment.js
index 07283b8..ee9c178 100644
--- a/tests/dummy/config/environment.js
+++ b/tests/dummy/config/environment.js
@@ -40,7 +40,7 @@ module.exports = function (environment) {
if (environment === 'production') {
ENV.locationType = 'hash'
- ENV.rootURL = '/ember-frost-modal'
+ ENV.rootURL = '/ember-frost-table'
}
return ENV
diff --git a/tests/helpers/selector-stub.js b/tests/helpers/selector-stub.js
new file mode 100644
index 0000000..186280f
--- /dev/null
+++ b/tests/helpers/selector-stub.js
@@ -0,0 +1,31 @@
+/**
+ * Test helper to stub a jQuery selector (something returned by this.$() within a component)
+ * TODO: maybe move to ember-test-utils
+ */
+
+import sinon from 'sinon'
+
+const defaultStubbedMethods = [
+ 'css',
+ 'find',
+ 'on',
+ 'scrollTop',
+ 'scrollLeft'
+]
+
+/**
+ * @param {String[]} stubbedMethods - the names of methods to stub on this selector stub
+ * @returns {*} a new selector stub
+ */
+export function createSelectorStub (...stubbedMethods) {
+ const stub = {}
+ if (stubbedMethods.length === 0) {
+ stubbedMethods = defaultStubbedMethods
+ }
+
+ stubbedMethods.forEach((method) => {
+ stub[method] = sinon.stub()
+ })
+
+ return stub
+}
diff --git a/tests/integration/components/data.js b/tests/integration/components/data.js
index c8e1d07..8abb8fc 100644
--- a/tests/integration/components/data.js
+++ b/tests/integration/components/data.js
@@ -15,6 +15,41 @@ export const columns = [
}
]
+export const fixedColumns = [
+ {
+ className: 'name-col',
+ frozen: true,
+ label: 'Name',
+ propertyName: 'name'
+ },
+ {
+ className: 'real-name-col',
+ label: 'Real Name',
+ propertyName: 'realName'
+ },
+ {
+ className: 'real-name-col',
+ label: 'Real Name',
+ propertyName: 'realName'
+ },
+ {
+ className: 'real-name-col',
+ label: 'Real Name',
+ propertyName: 'realName'
+ },
+ {
+ className: 'real-name-col',
+ label: 'Real Name',
+ propertyName: 'realName'
+ },
+ {
+ className: 'universe-col',
+ frozen: true,
+ label: 'Universe',
+ propertyName: 'universe'
+ }
+]
+
export const heroes = [
{
name: 'Superman',
diff --git a/tests/integration/components/frost-fixed-table-test.js b/tests/integration/components/frost-fixed-table-test.js
new file mode 100644
index 0000000..78d82e2
--- /dev/null
+++ b/tests/integration/components/frost-fixed-table-test.js
@@ -0,0 +1,330 @@
+/**
+ * Integration test for the frost-fixed-table component
+ */
+
+import {expect} from 'chai'
+import hbs from 'htmlbars-inline-precompile'
+import {$hook} from 'ember-hook'
+import wait from 'ember-test-helpers/wait'
+import {afterEach, beforeEach, describe, it} from 'mocha'
+import sinon from 'sinon'
+
+import {integration} from 'dummy/tests/helpers/ember-test-utils/setup-component-test'
+import {fixedColumns, heroes} from './data'
+
+const test = integration('frost-fixed-table')
+describe(test.label, function () {
+ test.setup()
+
+ let sandbox
+
+ beforeEach(function () {
+ sandbox = sinon.sandbox.create()
+ this.setProperties({
+ fixedColumns,
+ heroes,
+ myHook: 'myTable'
+ })
+ })
+
+ afterEach(function () {
+ sandbox.restore()
+ })
+
+ describe('after render', function () {
+ beforeEach(function () {
+ this.render(hbs`
+ {{frost-fixed-table
+ columns=fixedColumns
+ hook=myHook
+ items=heroes
+ }}
+ `)
+
+ return wait()
+ })
+
+ it('should create the header', function () {
+ expect(this.$('.frost-fixed-table-header')).to.have.length(1)
+ })
+
+ it('should create the body', function () {
+ expect(this.$('.frost-fixed-table-body')).to.have.length(1)
+ })
+
+ it('should set the hook of the body', function () {
+ expect($hook('myTable-body')).to.have.class('frost-fixed-table-body')
+ })
+
+ it('should not use
', function () {
+ expect(this.$('tbody')).to.have.length(0)
+ })
+
+ describe('the header', function () {
+ let $header
+ beforeEach(function () {
+ $header = $hook('myTable-header')
+ })
+
+ it('should be accessible via a hook', function () {
+ expect($header).to.have.length(1)
+ })
+
+ it('should create a left section', function () {
+ expect($header.find('.frost-fixed-table-header-left')).to.have.length(1)
+ })
+
+ it('should create a middle section', function () {
+ expect($header.find('.frost-fixed-table-header-middle')).to.have.length(1)
+ })
+
+ it('should create a right section', function () {
+ expect($header.find('.frost-fixed-table-header-right')).to.have.length(1)
+ })
+
+ it('should have a header cell per column', function () {
+ expect($header.find('.frost-table-header-cell')).to.have.length(fixedColumns.length)
+ })
+
+ describe('the left section', function () {
+ let $leftWrapper, $left
+ beforeEach(function () {
+ $leftWrapper = $header.find('.frost-fixed-table-header-left')
+ $left = $hook('myTable-header-left')
+ })
+
+ it('should be accessible via a hook', function () {
+ expect($left).to.have.length(1)
+ })
+
+ it('should live within the wrapper', function () {
+ expect($left.closest('.frost-fixed-table-header-left')).to.have.length(1)
+ })
+
+ it('should not have a frost-scroll wrapper', function () {
+ expect($leftWrapper.find('.frost-scroll')).to.have.length(0)
+ })
+
+ it('should be a frost-table-header', function () {
+ expect($left).to.have.class('frost-table-header')
+ })
+
+ it('should not use ', function () {
+ expect($leftWrapper.find('thead')).to.have.length(0)
+ })
+
+ it('should not use ', function () {
+ expect($leftWrapper.find('th')).to.have.length(0)
+ })
+ })
+
+ describe('the middle section', function () {
+ let $middleWrapper, $middle
+ beforeEach(function () {
+ $middleWrapper = $header.find('.frost-fixed-table-header-middle')
+ $middle = $hook('myTable-header-middle')
+ })
+
+ it('should be accessible via a hook', function () {
+ expect($middle).to.have.length(1)
+ })
+
+ it('should live within the wrapper', function () {
+ expect($middle.closest('.frost-fixed-table-header-middle')).to.have.length(1)
+ })
+
+ it('should have a frost-scroll wrapper', function () {
+ expect($middleWrapper.find('.frost-scroll')).to.have.length(1)
+ })
+
+ it('should be a frost-table-header', function () {
+ expect($middle).to.have.class('frost-table-header')
+ })
+
+ it('should not use ', function () {
+ expect($middleWrapper.find('thead')).to.have.length(0)
+ })
+
+ it('should not use ', function () {
+ expect($middleWrapper.find('th')).to.have.length(0)
+ })
+ })
+
+ describe('the right section', function () {
+ let $rightWrapper, $right
+ beforeEach(function () {
+ $rightWrapper = $header.find('.frost-fixed-table-header-right')
+ $right = $hook('myTable-header-right')
+ })
+
+ it('should be accessible via a hook', function () {
+ expect($right).to.have.length(1)
+ })
+
+ it('should live within the wrapper', function () {
+ expect($right.closest('.frost-fixed-table-header-right')).to.have.length(1)
+ })
+
+ it('should not have a frost-scroll wrapper', function () {
+ expect($rightWrapper.find('.frost-scroll')).to.have.length(0)
+ })
+
+ it('should be a frost-table-header', function () {
+ expect($right).to.have.class('frost-table-header')
+ })
+
+ it('should not use ', function () {
+ expect($rightWrapper.find('thead')).to.have.length(0)
+ })
+
+ it('should not use ', function () {
+ expect($rightWrapper.find('th')).to.have.length(0)
+ })
+ })
+ })
+
+ describe('the body', function () {
+ let $body
+ beforeEach(function () {
+ $body = $hook('myTable-body')
+ })
+
+ it('should be accessible via a hook', function () {
+ expect($body).to.have.length(1)
+ })
+
+ it('should create a left section', function () {
+ expect($body.find('.frost-fixed-table-left')).to.have.length(1)
+ })
+
+ it('should create a middle section', function () {
+ expect($body.find('.frost-fixed-table-middle')).to.have.length(1)
+ })
+
+ it('should create a right section', function () {
+ expect($body.find('.frost-fixed-table-right')).to.have.length(1)
+ })
+
+ it('should have a three rows per item', function () {
+ expect($body.find('.frost-table-row')).to.have.length(heroes.length * 3)
+ })
+
+ describe('the left section', function () {
+ const leftColumns = fixedColumns.slice(0, 1)
+ let $leftWrapper, $left
+
+ beforeEach(function () {
+ $leftWrapper = $body.find('.frost-fixed-table-left')
+ $left = $hook('myTable-left')
+ })
+
+ it('should have a cell for each left column for each item', function () {
+ expect($left.find('.frost-table-cell')).to.have.length(heroes.length * leftColumns.length)
+ })
+
+ heroes.forEach((hero, rowIndex) => {
+ leftColumns.forEach((column, columnIndex) => {
+ it(`should set a hook on the cell in row: ${rowIndex}, column: ${columnIndex}`, function () {
+ const $cell = $hook('myTable-left-cell', {row: rowIndex, column: columnIndex})
+ expect($cell.text().trim()).to.equal(hero[column.propertyName])
+ })
+ })
+ })
+
+ it('should live within the wrapper', function () {
+ expect($left.closest('.frost-fixed-table-left')).to.have.length(1)
+ })
+
+ it('should have a frost-scroll wrapper', function () {
+ expect($leftWrapper.find('.frost-scroll')).to.have.length(1)
+ })
+
+ it('should be a frost-table-row', function () {
+ expect($left).to.have.class('frost-table-row')
+ })
+
+ it('should not use | ', function () {
+ expect($leftWrapper.find('td')).to.have.length(0)
+ })
+ })
+
+ describe('the middle section', function () {
+ const middleColumns = fixedColumns.slice(1, 5)
+ let $middleWrapper, $middle
+
+ beforeEach(function () {
+ $middleWrapper = $body.find('.frost-fixed-table-middle')
+ $middle = $hook('myTable-middle')
+ })
+
+ it('should have a cell for each middle column for each item', function () {
+ expect($middle.find('.frost-table-cell')).to.have.length(heroes.length * middleColumns.length)
+ })
+
+ heroes.forEach((hero, rowIndex) => {
+ middleColumns.forEach((column, columnIndex) => {
+ it(`should set a hook on the cell in row: ${rowIndex}, column: ${columnIndex}`, function () {
+ const $cell = $hook('myTable-middle-cell', {row: rowIndex, column: columnIndex})
+ expect($cell.text().trim()).to.equal(hero[column.propertyName])
+ })
+ })
+ })
+
+ it('should live within the wrapper', function () {
+ expect($middle.closest('.frost-fixed-table-middle')).to.have.length(1)
+ })
+
+ it('should have a frost-scroll wrapper', function () {
+ expect($middleWrapper.find('.frost-scroll')).to.have.length(1)
+ })
+
+ it('should be a frost-table-row', function () {
+ expect($middle).to.have.class('frost-table-row')
+ })
+
+ it('should not use | ', function () {
+ expect($middleWrapper.find('td')).to.have.length(0)
+ })
+ })
+
+ describe('the right section', function () {
+ const rightColumns = fixedColumns.slice(5)
+ let $rightWrapper, $right
+
+ beforeEach(function () {
+ $rightWrapper = $body.find('.frost-fixed-table-right')
+ $right = $hook('myTable-right')
+ })
+
+ it('should have a cell for each right column for each item', function () {
+ expect($right.find('.frost-table-cell')).to.have.length(heroes.length * rightColumns.length)
+ })
+
+ heroes.forEach((hero, rowIndex) => {
+ rightColumns.forEach((column, columnIndex) => {
+ it(`should set a hook on the cell in row: ${rowIndex}, column: ${columnIndex}`, function () {
+ const $cell = $hook('myTable-right-cell', {row: rowIndex, column: columnIndex})
+ expect($cell.text().trim()).to.equal(hero[column.propertyName])
+ })
+ })
+ })
+
+ it('should live within the wrapper', function () {
+ expect($right.closest('.frost-fixed-table-right')).to.have.length(1)
+ })
+
+ it('should have a frost-scroll wrapper', function () {
+ expect($rightWrapper.find('.frost-scroll')).to.have.length(1)
+ })
+
+ it('should be a frost-table-row', function () {
+ expect($right).to.have.class('frost-table-row')
+ })
+
+ it('should not use | ', function () {
+ expect($rightWrapper.find('td')).to.have.length(0)
+ })
+ })
+ })
+ })
+})
diff --git a/tests/integration/components/frost-table-body-test.js b/tests/integration/components/frost-table-body-test.js
index b3ee80c..b5cea69 100644
--- a/tests/integration/components/frost-table-body-test.js
+++ b/tests/integration/components/frost-table-body-test.js
@@ -5,18 +5,19 @@
import {expect} from 'chai'
import Ember from 'ember'
const {$, get} = Ember
-import {describeComponent, it} from 'ember-mocha'
-import hbs from 'htmlbars-inline-precompile'
-import {$hook, initialize as initializeHook} from 'ember-hook'
+import {$hook} from 'ember-hook'
import wait from 'ember-test-helpers/wait'
-import {beforeEach, describe} from 'mocha'
+import hbs from 'htmlbars-inline-precompile'
+import {beforeEach, describe, it} from 'mocha'
-import {integration} from 'dummy/tests/helpers/ember-test-utils/describe-component'
+import {integration} from 'dummy/tests/helpers/ember-test-utils/setup-component-test'
import {columns, heroes} from './data'
-describeComponent(...integration('frost-table-body'), function () {
+const test = integration('frost-table-body')
+describe(test.label, function () {
+ test.setup()
+
beforeEach(function () {
- initializeHook()
this.setProperties({
columns,
heroes,
diff --git a/tests/integration/components/frost-table-cell-test.js b/tests/integration/components/frost-table-cell-test.js
new file mode 100644
index 0000000..0b7b764
--- /dev/null
+++ b/tests/integration/components/frost-table-cell-test.js
@@ -0,0 +1,56 @@
+/**
+ * Integration test for the frost-table-cell component
+ */
+
+import {expect} from 'chai'
+import {$hook} from 'ember-hook'
+import wait from 'ember-test-helpers/wait'
+import hbs from 'htmlbars-inline-precompile'
+import {afterEach, beforeEach, describe, it} from 'mocha'
+import sinon from 'sinon'
+
+import {integration} from 'dummy/tests/helpers/ember-test-utils/setup-component-test'
+
+const test = integration('frost-table-cell')
+describe(test.label, function () {
+ test.setup()
+
+ let sandbox
+
+ beforeEach(function () {
+ sandbox = sinon.sandbox.create()
+ })
+
+ afterEach(function () {
+ sandbox.restore()
+ })
+
+ // FIXME: actually add real tests in next PR when frost-table-cell supports custom renderer components
+ it.skip('should have real tests', function () {
+ expect(true).to.equal(false)
+ })
+
+ describe('after render', function () {
+ beforeEach(function () {
+ this.setProperties({
+ myHook: 'myThing'
+ })
+
+ this.render(hbs`
+ {{frost-table-cell
+ hook=myHook
+ }}
+ `)
+
+ return wait()
+ })
+
+ it('should have an element', function () {
+ expect(this.$()).to.have.length(1)
+ })
+
+ it('should be accessible via the hook', function () {
+ expect($hook('myThing')).to.have.length(1)
+ })
+ })
+})
diff --git a/tests/integration/components/frost-table-header-test.js b/tests/integration/components/frost-table-header-test.js
index 8e86b2b..8abfd98 100644
--- a/tests/integration/components/frost-table-header-test.js
+++ b/tests/integration/components/frost-table-header-test.js
@@ -5,18 +5,19 @@
import {expect} from 'chai'
import Ember from 'ember'
const {$} = Ember
-import {describeComponent, it} from 'ember-mocha'
-import hbs from 'htmlbars-inline-precompile'
-import {$hook, initialize as initializeHook} from 'ember-hook'
+import {$hook} from 'ember-hook'
import wait from 'ember-test-helpers/wait'
-import {beforeEach, describe} from 'mocha'
+import hbs from 'htmlbars-inline-precompile'
+import {beforeEach, describe, it} from 'mocha'
-import {integration} from 'dummy/tests/helpers/ember-test-utils/describe-component'
+import {integration} from 'dummy/tests/helpers/ember-test-utils/setup-component-test'
import {columns} from './data'
-describeComponent(...integration('frost-table-header'), function () {
+const test = integration('frost-table-header')
+describe(test.label, function () {
+ test.setup()
+
beforeEach(function () {
- initializeHook()
this.setProperties({
columns,
myHook: 'myTableHeader'
diff --git a/tests/integration/components/frost-table-row-test.js b/tests/integration/components/frost-table-row-test.js
index e9f82db..61dbbac 100644
--- a/tests/integration/components/frost-table-row-test.js
+++ b/tests/integration/components/frost-table-row-test.js
@@ -5,18 +5,19 @@
import {expect} from 'chai'
import Ember from 'ember'
const {$, get} = Ember
-import {describeComponent, it} from 'ember-mocha'
import hbs from 'htmlbars-inline-precompile'
-import {$hook, initialize as initializeHook} from 'ember-hook'
+import {$hook} from 'ember-hook'
import wait from 'ember-test-helpers/wait'
-import {beforeEach, describe} from 'mocha'
+import {beforeEach, describe, it} from 'mocha'
-import {integration} from 'dummy/tests/helpers/ember-test-utils/describe-component'
+import {integration} from 'dummy/tests/helpers/ember-test-utils/setup-component-test'
import {columns, heroes} from './data'
-describeComponent(...integration('frost-table-body-row'), function () {
+const test = integration('frost-table-body-row')
+describe(test.label, function () {
+ test.setup()
+
beforeEach(function () {
- initializeHook()
this.setProperties({
columns,
hero: heroes[2],
diff --git a/tests/integration/components/frost-table-test.js b/tests/integration/components/frost-table-test.js
index 3af60a4..4498088 100644
--- a/tests/integration/components/frost-table-test.js
+++ b/tests/integration/components/frost-table-test.js
@@ -5,18 +5,19 @@
import {expect} from 'chai'
import Ember from 'ember'
const {$, get} = Ember
-import {describeComponent, it} from 'ember-mocha'
import hbs from 'htmlbars-inline-precompile'
-import {$hook, initialize as initializeHook} from 'ember-hook'
+import {$hook} from 'ember-hook'
import wait from 'ember-test-helpers/wait'
-import {beforeEach, describe} from 'mocha'
+import {beforeEach, describe, it} from 'mocha'
-import {integration} from 'dummy/tests/helpers/ember-test-utils/describe-component'
+import {integration} from 'dummy/tests/helpers/ember-test-utils/setup-component-test'
import {columns, heroes} from './data'
-describeComponent(...integration('frost-table'), function () {
+const test = integration('frost-table')
+describe(test.label, function () {
+ test.setup()
+
beforeEach(function () {
- initializeHook()
this.setProperties({
columns,
heroes,
diff --git a/tests/unit/.gitkeep b/tests/unit/.gitkeep
deleted file mode 100644
index e69de29..0000000
diff --git a/tests/unit/components/frost-fixed-table-test.js b/tests/unit/components/frost-fixed-table-test.js
new file mode 100644
index 0000000..0e2b743
--- /dev/null
+++ b/tests/unit/components/frost-fixed-table-test.js
@@ -0,0 +1,558 @@
+/**
+ * Unit test for the frost-fixed-table component
+ *
+ * NOTE: Since it is not easy to properly set up an integration test to confirm some of the DOM
+ * caluclations happening in frost-fixed-table, I opted to unit test these calculations, making these
+ * tests a little more tied to the implementation than I'd like. However, given the hoops needed to jump through to
+ * simulate external CSS as well as scroll and mouse events, this seemed the better option (ARM 2016-12-13)
+ */
+
+import {expect} from 'chai'
+import {afterEach, beforeEach, describe, it} from 'mocha'
+import sinon from 'sinon'
+
+import {unit} from 'dummy/tests/helpers/ember-test-utils/setup-component-test'
+import {createSelectorStub} from 'dummy/tests/helpers/selector-stub'
+
+const test = unit('frost-fixed-table')
+describe(test.label, function () {
+ test.setup()
+
+ let component, columns, sandbox
+
+ beforeEach(function () {
+ sandbox = sinon.sandbox.create()
+ component = this.subject({tagName: 'div'})
+ columns = [
+ {
+ frozen: true,
+ propertyName: 'name'
+ },
+ {
+ frozen: true,
+ propertyName: 'description'
+ },
+ {
+ propertyName: 'info1'
+ },
+ {
+ propertyName: 'info2'
+ },
+ {
+ propertyName: 'info3'
+ },
+ {
+ frozen: true,
+ propertyName: 'summary1'
+ },
+ {
+ frozen: true,
+ propertyName: 'summary2'
+ }
+ ]
+ })
+
+ afterEach(function () {
+ sandbox.restore()
+ })
+
+ describe('Computed Properties', function () {
+ const cpExpectedValues = {
+ bodyLeftSelector: '.frost-fixed-table-left .frost-scroll',
+ bodyMiddleSelector: '.frost-fixed-table-middle .frost-scroll',
+ bodyRightSelector: '.frost-fixed-table-right .frost-scroll',
+ headerMiddleSelector: '.frost-fixed-table-header-middle .frost-scroll'
+ }
+
+ for (let key in cpExpectedValues) {
+ describe(key, function () {
+ let value
+ beforeEach(function () {
+ value = component.get(key)
+ })
+
+ it('should return the expected value', function () {
+ expect(value).to.equal(cpExpectedValues[key])
+ })
+ })
+ }
+
+ describe('leftColumns', function () {
+ describe('with properly ordered columns', function () {
+ beforeEach(function () {
+ component.setProperties({columns})
+ })
+
+ it('should have the first frozen columns', function () {
+ expect(component.get('leftColumns')).to.eql(columns.slice(0, 2))
+ })
+ })
+
+ describe('with no leading frozen columns', function () {
+ beforeEach(function () {
+ component.set('columns', columns.slice(2))
+ })
+
+ it('should be empty', function () {
+ expect(component.get('leftColumns')).to.eql([])
+ })
+ })
+ })
+
+ describe('middleColumns', function () {
+ describe('with properly ordered columns', function () {
+ beforeEach(function () {
+ component.setProperties({columns})
+ })
+
+ it('should have the middle non-frozen columns', function () {
+ expect(component.get('middleColumns')).to.eql(columns.slice(2, 5))
+ })
+ })
+
+ describe('with no leading frozen columns', function () {
+ beforeEach(function () {
+ component.set('columns', columns.slice(2))
+ })
+
+ it('should have the first non-frozen columns', function () {
+ expect(component.get('middleColumns')).to.eql(columns.slice(2, 5))
+ })
+ })
+
+ describe('with no trailing frozen columns', function () {
+ beforeEach(function () {
+ component.set('columns', columns.slice(0, 5))
+ })
+
+ it('should have the last non-frozen columns', function () {
+ expect(component.get('middleColumns')).to.eql(columns.slice(2, 5))
+ })
+ })
+
+ describe('with no unfrozen columns', function () {
+ beforeEach(function () {
+ component.set('columns', columns.slice(0, 2))
+ })
+
+ it('should be empty', function () {
+ expect(component.get('middleColumns')).to.eql([])
+ })
+ })
+ })
+
+ describe('rightColumns', function () {
+ describe('with properly ordered columns', function () {
+ beforeEach(function () {
+ component.setProperties({columns})
+ })
+
+ it('should have the last frozen columns', function () {
+ expect(component.get('rightColumns')).to.eql(columns.slice(-2))
+ })
+ })
+
+ describe('with no trailing frozen columns', function () {
+ beforeEach(function () {
+ component.set('columns', columns.slice(0, 5))
+ })
+
+ it('should be empty', function () {
+ expect(component.get('rightColumns')).to.eql([])
+ })
+ })
+ })
+ })
+
+ describe('.didRender()', function () {
+ beforeEach(function () {
+ sandbox.stub(component, 'setupBodyHeights')
+ sandbox.stub(component, 'setupHoverProxy')
+ sandbox.stub(component, 'setupMiddleMargins')
+ sandbox.stub(component, 'setupMiddleWidths')
+ sandbox.stub(component, 'setupScrollSync')
+
+ component.didRender()
+ })
+
+ it('should set up the body heights', function () {
+ expect(component.setupBodyHeights).to.have.callCount(1)
+ })
+
+ it('should set up the hover proxy', function () {
+ expect(component.setupHoverProxy).to.have.callCount(1)
+ })
+
+ it('should set up the middle margins', function () {
+ expect(component.setupMiddleMargins).to.have.callCount(1)
+ })
+
+ it('should set up the middle widths', function () {
+ expect(component.setupMiddleWidths).to.have.callCount(1)
+ })
+
+ it('should set up the scroll syncing', function () {
+ expect(component.setupScrollSync).to.have.callCount(1)
+ })
+ })
+
+ describe('._calculateWidth()', function () {
+ // TODO: add tests, need to figure out how to stub Ember.$ properly for this one
+
+ })
+
+ describe('.setupBodyHeights()', function () {
+ let leftBodyStub, middleHeaderStub, middleBodyStub, rightBodyStub, tableStub
+ beforeEach(function () {
+ leftBodyStub = createSelectorStub('css')
+ middleBodyStub = createSelectorStub('css')
+ middleHeaderStub = createSelectorStub('outerHeight')
+ rightBodyStub = createSelectorStub('css')
+ tableStub = createSelectorStub('outerHeight')
+
+ sandbox.stub(component, '$')
+ .withArgs('.frost-fixed-table-left .frost-scroll').returns(leftBodyStub)
+ .withArgs('.frost-fixed-table-middle .frost-scroll').returns(middleBodyStub)
+ .withArgs('.frost-fixed-table-header-middle .frost-scroll').returns(middleHeaderStub)
+ .withArgs('.frost-fixed-table-right .frost-scroll').returns(rightBodyStub)
+ .withArgs().returns(tableStub)
+
+ tableStub.outerHeight.returns(100)
+ middleHeaderStub.outerHeight.returns(20)
+
+ component.setupBodyHeights()
+ })
+
+ it('should set the height of the left body', function () {
+ expect(leftBodyStub.css).to.have.been.calledWith({height: '80px'})
+ })
+
+ it('should set the height of the middle body', function () {
+ expect(middleBodyStub.css).to.have.been.calledWith({height: '80px'})
+ })
+
+ it('should set the height of the right body', function () {
+ expect(rightBodyStub.css).to.have.been.calledWith({height: '80px'})
+ })
+ })
+
+ describe('.setupHoverProxy()', function () {
+ let leftBodyStub, middleHeaderStub, middleBodyStub, rightBodyStub
+ let leftBodyMouseEnterHandler, leftBodyMouseLeaveHandler
+ let middleBodyMouseEnterHandler, middleBodyMouseLeaveHandler
+ let middleHeaderMouseEnterHandler, middleHeaderMouseLeaveHandler
+
+ beforeEach(function () {
+ leftBodyStub = createSelectorStub('on')
+ middleBodyStub = createSelectorStub('on', 'addClass', 'removeClass')
+ middleHeaderStub = createSelectorStub('on')
+ rightBodyStub = createSelectorStub('addClass', 'removeClass')
+
+ sandbox.stub(component, '$')
+ .withArgs('.frost-fixed-table-left .frost-scroll').returns(leftBodyStub)
+ .withArgs('.frost-fixed-table-middle .frost-scroll').returns(middleBodyStub)
+ .withArgs('.frost-fixed-table-header-middle .frost-scroll').returns(middleHeaderStub)
+ .withArgs('.frost-fixed-table-right .frost-scroll').returns(rightBodyStub)
+
+ component.setupHoverProxy()
+
+ // capture the event handlers
+ leftBodyMouseEnterHandler = leftBodyStub.on.getCall(0).args[1]
+ leftBodyMouseLeaveHandler = leftBodyStub.on.getCall(1).args[1]
+ middleBodyMouseEnterHandler = middleBodyStub.on.getCall(0).args[1]
+ middleBodyMouseLeaveHandler = middleBodyStub.on.getCall(1).args[1]
+ middleHeaderMouseEnterHandler = middleHeaderStub.on.getCall(0).args[1]
+ middleHeaderMouseLeaveHandler = middleHeaderStub.on.getCall(1).args[1]
+ })
+
+ it('should add mouseenter handler to left body', function () {
+ expect(leftBodyStub.on).to.have.been.calledWith('mouseenter', sinon.match.func)
+ })
+
+ it('should add mouseleave handler to left body', function () {
+ expect(leftBodyStub.on).to.have.been.calledWith('mouseleave', sinon.match.func)
+ })
+
+ it('should add mouseenter handler to middle body', function () {
+ expect(middleBodyStub.on).to.have.been.calledWith('mouseenter', sinon.match.func)
+ })
+
+ it('should add mouseleave handler to middle body', function () {
+ expect(middleBodyStub.on).to.have.been.calledWith('mouseleave', sinon.match.func)
+ })
+
+ it('should add mouseenter handler to middle header', function () {
+ expect(middleHeaderStub.on).to.have.been.calledWith('mouseenter', sinon.match.func)
+ })
+
+ it('should add mouseleave handler to middle header', function () {
+ expect(middleHeaderStub.on).to.have.been.calledWith('mouseleave', sinon.match.func)
+ })
+
+ describe('when left body is hovered', function () {
+ beforeEach(function () {
+ leftBodyMouseEnterHandler()
+ })
+
+ it('should add the hover class to the right body', function () {
+ expect(rightBodyStub.addClass).to.have.been.calledWith('ps-container-hover')
+ })
+ })
+
+ describe('when left body is un-hovered', function () {
+ beforeEach(function () {
+ leftBodyMouseLeaveHandler()
+ })
+
+ it('should remove the hover class from the right body', function () {
+ expect(rightBodyStub.removeClass).to.have.been.calledWith('ps-container-hover')
+ })
+ })
+
+ describe('when middle body is hovered', function () {
+ beforeEach(function () {
+ middleBodyMouseEnterHandler()
+ })
+
+ it('should add the hover class to the right body', function () {
+ expect(rightBodyStub.addClass).to.have.been.calledWith('ps-container-hover')
+ })
+ })
+
+ describe('when middle body is un-hovered', function () {
+ beforeEach(function () {
+ middleBodyMouseLeaveHandler()
+ })
+
+ it('should remove the hover class from the right body', function () {
+ expect(rightBodyStub.removeClass).to.have.been.calledWith('ps-container-hover')
+ })
+ })
+
+ describe('when middle header is hovered', function () {
+ beforeEach(function () {
+ middleHeaderMouseEnterHandler()
+ })
+
+ it('should add the hover class to the middle body', function () {
+ expect(middleBodyStub.addClass).to.have.been.calledWith('ps-container-hover')
+ })
+ })
+
+ describe('when middle header is un-hovered', function () {
+ beforeEach(function () {
+ middleHeaderMouseLeaveHandler()
+ })
+
+ it('should remove the hover class from the middle body', function () {
+ expect(middleBodyStub.removeClass).to.have.been.calledWith('ps-container-hover')
+ })
+ })
+ })
+
+ describe('.setupMiddleMargins()', function () {
+ let leftBodyStub, middleHeaderStub, middleBodyStub, rightBodyStub
+ beforeEach(function () {
+ leftBodyStub = createSelectorStub('outerWidth')
+ middleBodyStub = createSelectorStub('css')
+ middleHeaderStub = createSelectorStub('css')
+ rightBodyStub = createSelectorStub('outerWidth')
+
+ sandbox.stub(component, '$')
+ .withArgs('.frost-fixed-table-left .frost-scroll').returns(leftBodyStub)
+ .withArgs('.frost-fixed-table-middle .frost-scroll').returns(middleBodyStub)
+ .withArgs('.frost-fixed-table-header-middle .frost-scroll').returns(middleHeaderStub)
+ .withArgs('.frost-fixed-table-right .frost-scroll').returns(rightBodyStub)
+
+ leftBodyStub.outerWidth.returns(123)
+ rightBodyStub.outerWidth.returns(321)
+
+ component.setupMiddleMargins()
+ })
+
+ it('should set proper margins on the middle header', function () {
+ expect(middleHeaderStub.css).to.have.been.calledWith({
+ 'margin-left': '123px',
+ 'margin-right': '321px'
+ })
+ })
+
+ it('should set proper margins on the middle body', function () {
+ expect(middleBodyStub.css).to.have.been.calledWith({
+ 'margin-left': '123px',
+ 'margin-right': '321px'
+ })
+ })
+ })
+
+ describe('.setupMiddleWidths()', function () {
+ let middleHeaderStub, middleBodyStub
+ beforeEach(function () {
+ sandbox.stub(component, '_calculateWidth').returns(12345)
+ middleHeaderStub = createSelectorStub('css')
+ middleBodyStub = createSelectorStub('css')
+ sandbox.stub(component, '$')
+ .withArgs('.frost-fixed-table-header-middle .frost-scroll .frost-table-header').returns(middleHeaderStub)
+ .withArgs('.frost-fixed-table-middle .frost-scroll .frost-table-row').returns(middleBodyStub)
+
+ component.setupMiddleWidths()
+ })
+
+ it('should set width of middle header', function () {
+ expect(middleHeaderStub.css).to.have.been.calledWith({width: '12345px'})
+ })
+
+ it('should set width of middle body', function () {
+ expect(middleBodyStub.css).to.have.been.calledWith({width: '12345px'})
+ })
+ })
+
+ describe('.setupScrollSync()', function () {
+ beforeEach(function () {
+ sandbox.stub(component, 'syncScrollLeft')
+ sandbox.stub(component, 'syncScrollTop')
+
+ component.setupScrollSync()
+ })
+
+ it('should setup horizontal syncing from header middle to body middle', function () {
+ expect(component.syncScrollLeft).to.have.been.calledWith(
+ '.frost-fixed-table-header-middle .frost-scroll',
+ '.frost-fixed-table-middle .frost-scroll'
+ )
+ })
+
+ it('should setup horizontal syncing from body middle to header middle', function () {
+ expect(component.syncScrollLeft).to.have.been.calledWith(
+ '.frost-fixed-table-middle .frost-scroll',
+ '.frost-fixed-table-header-middle .frost-scroll'
+ )
+ })
+
+ it('should setup vertical syncing from body left to body middle and right', function () {
+ expect(component.syncScrollTop).to.have.been.calledWith(
+ '.frost-fixed-table-left .frost-scroll',
+ '.frost-fixed-table-middle .frost-scroll',
+ '.frost-fixed-table-right .frost-scroll'
+ )
+ })
+
+ it('should setup vertical syncing from body middle to body left and right', function () {
+ expect(component.syncScrollTop).to.have.been.calledWith(
+ '.frost-fixed-table-middle .frost-scroll',
+ '.frost-fixed-table-left .frost-scroll',
+ '.frost-fixed-table-right .frost-scroll'
+ )
+ })
+
+ it('should setup vertical syncing from body right to body left and middle', function () {
+ expect(component.syncScrollTop).to.have.been.calledWith(
+ '.frost-fixed-table-right .frost-scroll',
+ '.frost-fixed-table-left .frost-scroll',
+ '.frost-fixed-table-middle .frost-scroll'
+ )
+ })
+ })
+
+ describe('.syncScrollLeft()', function () {
+ let srcStub, scrollHandler
+ beforeEach(function () {
+ srcStub = createSelectorStub('on', 'scrollLeft')
+ sandbox.stub(component, '$').withArgs('src').returns(srcStub)
+ component.syncScrollLeft('src', 'dst1', 'dst2', 'dst3')
+ scrollHandler = srcStub.on.lastCall.args[1]
+ })
+
+ it('should lookup the source DOM element', function () {
+ expect(component.$).to.have.been.calledWith('src')
+ })
+
+ it('should add a scroll event handler to the source DOM element', function () {
+ expect(srcStub.on).to.have.been.calledWith('scroll', sinon.match.func)
+ })
+
+ describe('when the scroll even handler is called', function () {
+ let dst1Stub, dst2Stub, dst3Stub
+ beforeEach(function () {
+ dst1Stub = createSelectorStub('scrollLeft')
+ dst2Stub = createSelectorStub('scrollLeft')
+ dst3Stub = createSelectorStub('scrollLeft')
+ component.$.withArgs('dst1').returns(dst1Stub)
+ component.$.withArgs('dst2').returns(dst2Stub)
+ component.$.withArgs('dst3').returns(dst3Stub)
+
+ srcStub.scrollLeft.returns(321)
+
+ component.$.reset() // forget previous call
+ scrollHandler()
+ })
+
+ it('should lookup the src DOM again', function () {
+ expect(component.$).to.have.been.calledWith('src')
+ })
+
+ it('should set scrollLeft on the first destination', function () {
+ expect(dst1Stub.scrollLeft).to.have.been.calledWith(321)
+ })
+
+ it('should set scrollLeft on the second destination', function () {
+ expect(dst2Stub.scrollLeft).to.have.been.calledWith(321)
+ })
+
+ it('should set scrollLeft on the third destination', function () {
+ expect(dst3Stub.scrollLeft).to.have.been.calledWith(321)
+ })
+ })
+ })
+
+ describe('.syncScrollTop()', function () {
+ let srcStub, scrollHandler
+ beforeEach(function () {
+ srcStub = createSelectorStub('on', 'scrollTop')
+ sandbox.stub(component, '$').withArgs('src').returns(srcStub)
+ component.syncScrollTop('src', 'dst1', 'dst2', 'dst3')
+ scrollHandler = srcStub.on.lastCall.args[1]
+ })
+
+ it('should lookup the source DOM element', function () {
+ expect(component.$).to.have.been.calledWith('src')
+ })
+
+ it('should add a scroll event handler to the source DOM element', function () {
+ expect(srcStub.on).to.have.been.calledWith('scroll', sinon.match.func)
+ })
+
+ describe('when the scroll even handler is called', function () {
+ let dst1Stub, dst2Stub, dst3Stub
+ beforeEach(function () {
+ dst1Stub = createSelectorStub('scrollTop')
+ dst2Stub = createSelectorStub('scrollTop')
+ dst3Stub = createSelectorStub('scrollTop')
+ component.$.withArgs('dst1').returns(dst1Stub)
+ component.$.withArgs('dst2').returns(dst2Stub)
+ component.$.withArgs('dst3').returns(dst3Stub)
+
+ srcStub.scrollTop.returns(123)
+
+ component.$.reset() // forget previous call
+ scrollHandler()
+ })
+
+ it('should lookup the src DOM again', function () {
+ expect(component.$).to.have.been.calledWith('src')
+ })
+
+ it('should set scrollTop on the first destination', function () {
+ expect(dst1Stub.scrollTop).to.have.been.calledWith(123)
+ })
+
+ it('should set scrollTop on the second destination', function () {
+ expect(dst2Stub.scrollTop).to.have.been.calledWith(123)
+ })
+
+ it('should set scrollTop on the third destination', function () {
+ expect(dst3Stub.scrollTop).to.have.been.calledWith(123)
+ })
+ })
+ })
+})
diff --git a/tests/unit/helpers/extend-test.js b/tests/unit/helpers/extend-test.js
deleted file mode 100644
index 5619a0c..0000000
--- a/tests/unit/helpers/extend-test.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * Unit test for the extend helper
- */
-import {expect} from 'chai'
-import {beforeEach, describe, it} from 'mocha'
-
-import {extend} from 'ember-frost-table/helpers/extend'
-
-describe('Unit / Helper / extend', function () {
- let original, extended
- beforeEach(function () {
- original = {
- bar: 'baz',
- baz: 'foo',
- foo: 'bar'
- }
- extended = extend([original], {fizz: 'bang'})
- })
-
- it('should leave the original object alone', function () {
- expect(original).to.eql({
- bar: 'baz',
- baz: 'foo',
- foo: 'bar'
- })
- })
-
- it('should return the merged object', function () {
- expect(extended).to.eql({
- bar: 'baz',
- baz: 'foo',
- fizz: 'bang',
- foo: 'bar'
- })
- })
-})
| | |