diff --git a/app/common/state/tabContentState/audioState.js b/app/common/state/tabContentState/audioState.js
new file mode 100644
index 00000000000..49b8b5835d8
--- /dev/null
+++ b/app/common/state/tabContentState/audioState.js
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// State helpers
+const frameStateUtil = require('../../../../js/state/frameStateUtil')
+
+// Utils
+const {isEntryIntersected} = require('../../../../app/renderer/lib/observerUtil')
+
+module.exports.canPlayAudio = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return false
+ }
+
+ return frame.get('audioPlaybackActive') || frame.get('audioMuted')
+}
+
+module.exports.isAudioMuted = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return false
+ }
+
+ const tabCanPlayAudio = module.exports.canPlayAudio(state, frameKey)
+ return tabCanPlayAudio && frame.get('audioMuted')
+}
+
+module.exports.showAudioIcon = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return false
+ }
+
+ return (
+ !isEntryIntersected(state, 'tabs') &&
+ module.exports.canPlayAudio(state, frameKey)
+ )
+}
+
+module.exports.showAudioTopBorder = (state, frameKey, isPinned) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return false
+ }
+
+ return (
+ module.exports.canPlayAudio(state, frameKey) &&
+ (isEntryIntersected(state, 'tabs') || isPinned)
+ )
+}
diff --git a/app/common/state/tabContentState/closeState.js b/app/common/state/tabContentState/closeState.js
new file mode 100644
index 00000000000..4fcc279baea
--- /dev/null
+++ b/app/common/state/tabContentState/closeState.js
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// State helpers
+const frameStateUtil = require('../../../../js/state/frameStateUtil')
+
+// Utils
+const {isEntryIntersected} = require('../../../../app/renderer/lib/observerUtil')
+
+// Styles
+const {intersection} = require('../../../renderer/components/styles/global')
+
+module.exports.hasFixedCloseIcon = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return false
+ }
+
+ return (
+ frameStateUtil.isFrameKeyActive(state, frameKey) &&
+ isEntryIntersected(state, 'tabs', intersection.at75)
+ )
+}
+
+module.exports.hasRelativeCloseIcon = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return false
+ }
+
+ return (
+ frameStateUtil.getTabHoverState(state, frameKey) &&
+ !isEntryIntersected(state, 'tabs', intersection.at75)
+ )
+}
+
+module.exports.showCloseTabIcon = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return false
+ }
+
+ return !isEntryIntersected(state, 'tabs', intersection.at20) &&
+ (
+ module.exports.hasRelativeCloseIcon(state, frameKey) ||
+ module.exports.hasFixedCloseIcon(state, frameKey)
+ )
+}
diff --git a/app/common/state/tabContentState/faviconState.js b/app/common/state/tabContentState/faviconState.js
new file mode 100644
index 00000000000..16bac75e219
--- /dev/null
+++ b/app/common/state/tabContentState/faviconState.js
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Utils
+const {isSourceAboutUrl} = require('../../../../js/lib/appUrlUtil')
+const frameStateUtil = require('../../../../js/state/frameStateUtil')
+const {isEntryIntersected} = require('../../../../app/renderer/lib/observerUtil')
+
+// Styles
+const {intersection} = require('../../../renderer/components/styles/global')
+
+module.exports.showFavicon = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return false
+ }
+
+ const isNewTabPage = frameStateUtil.frameLocationMatch(frame, 'about:newtab')
+
+ if (isEntryIntersected(state, 'tabs', intersection.at40)) {
+ // do not show it at all at minimum ratio (intersection.at12)
+ if (isEntryIntersected(state, 'tabs', intersection.at12)) {
+ return false
+ }
+
+ return (
+ // when almost all tab content is covered,
+ // only show favicon if there's no closeIcon (intersection.at20)
+ // or otherwise only for the non-active tab
+ isEntryIntersected(state, 'tabs', intersection.at20) ||
+ !frameStateUtil.isFrameKeyActive(state, frameKey)
+ )
+ }
+
+ // new tab page is the only tab we do not show favicon
+ return !isNewTabPage
+}
+
+module.exports.getFavicon = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+ const isLoadingVisible = module.exports.showLoadingIcon(state, frameKey)
+
+ if (frame == null) {
+ return ''
+ }
+
+ return !isLoadingVisible && frame.get('icon')
+}
+
+module.exports.showLoadingIcon = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return false
+ }
+
+ if (frame.get('loading') == null) {
+ return false
+ }
+
+ return (
+ !isSourceAboutUrl(frame.get('location')) &&
+ frame.get('loading')
+ )
+}
+
+module.exports.showIconWithLessMargin = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return false
+ }
+
+ return isEntryIntersected(state, 'tabs', intersection.at30)
+}
+
+module.exports.showFaviconAtReducedSize = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return false
+ }
+
+ return isEntryIntersected(state, 'tabs', intersection.at20)
+}
diff --git a/app/common/state/tabContentState/partitionState.js b/app/common/state/tabContentState/partitionState.js
new file mode 100644
index 00000000000..425bd08d7e6
--- /dev/null
+++ b/app/common/state/tabContentState/partitionState.js
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// State helpers
+const tabUIState = require('../tabUIState')
+const frameStateUtil = require('../../../../js/state/frameStateUtil')
+
+// Constants
+const {tabs} = require('../../../../js/constants/config')
+
+module.exports.isPartitionTab = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return false
+ }
+
+ return !!frame.get('partitionNumber')
+}
+
+module.exports.getPartitionNumber = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return 0
+ }
+
+ const partitionNumber = frame.get('partitionNumber')
+ if (typeof partitionNumber === 'string') {
+ return partitionNumber.replace(/^partition-/i, '')
+ }
+ return partitionNumber
+}
+
+module.exports.getMaxAllowedPartitionNumber = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return 0
+ }
+
+ const partitionNumber = module.exports.getPartitionNumber(state, frameKey)
+
+ if (partitionNumber > tabs.maxAllowedNewSessions) {
+ return tabs.maxAllowedNewSessions
+ }
+ return partitionNumber
+}
+
+module.exports.showPartitionIcon = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ if (process.env.NODE_ENV !== 'test') {
+ console.error('Unable to find frame for showPartitionIcon method')
+ }
+ return false
+ }
+
+ return (
+ module.exports.isPartitionTab(state, frameKey) &&
+ tabUIState.showTabEndIcon(state, frameKey)
+ )
+}
diff --git a/app/common/state/tabContentState/privateState.js b/app/common/state/tabContentState/privateState.js
new file mode 100644
index 00000000000..727aa632316
--- /dev/null
+++ b/app/common/state/tabContentState/privateState.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// State helpers
+const tabUIState = require('../tabUIState')
+const frameStateUtil = require('../../../../js/state/frameStateUtil')
+
+module.exports.isPrivateTab = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return false
+ }
+
+ return !!frame.get('isPrivate')
+}
+
+module.exports.showPrivateIcon = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return false
+ }
+
+ return (
+ module.exports.isPrivateTab(state, frameKey) &&
+ tabUIState.showTabEndIcon(state, frameKey)
+ )
+}
diff --git a/app/common/state/tabContentState/titleState.js b/app/common/state/tabContentState/titleState.js
new file mode 100644
index 00000000000..6bdc5b75f47
--- /dev/null
+++ b/app/common/state/tabContentState/titleState.js
@@ -0,0 +1,64 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ const locale = require('../../../../js/l10n')
+
+ // State helpers
+ const partitionState = require('../tabContentState/partitionState')
+ const privateState = require('../tabContentState/privateState')
+ const frameStateUtil = require('../../../../js/state/frameStateUtil')
+
+ // Utils
+ const {isEntryIntersected} = require('../../../../app/renderer/lib/observerUtil')
+
+ // Styles
+ const {intersection} = require('../../../renderer/components/styles/global')
+
+ module.exports.showTabTitle = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return false
+ }
+
+ const isNewTabPage = frameStateUtil.frameLocationMatch(frame, 'about:newtab')
+ const isActive = frameStateUtil.isFrameKeyActive(state, frameKey)
+ const isPartition = partitionState.isPartitionTab(state, frameKey)
+ const isPrivate = privateState.isPrivateTab(state, frameKey)
+ const secondaryIconVisible = !isNewTabPage && (isPartition || isPrivate || isActive)
+
+ // If title is being intersected by ~half with other icons visible
+ // such as closeTab (activeTab) or session icons, do not show it
+ if (isEntryIntersected(state, 'tabs', intersection.at45) && secondaryIconVisible) {
+ return false
+ }
+
+ // title should never show at such intersection point
+ return !isEntryIntersected(state, 'tabs', intersection.at40)
+ }
+
+ module.exports.getDisplayTitle = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ return ''
+ }
+
+ const isNewTabPage = frameStateUtil.frameLocationMatch(frame, 'about:newtab')
+ const isAboutBlankPage = frameStateUtil.frameLocationMatch(frame, 'about:blank')
+ // For renderer initiated navigation, make sure we show Untitled
+ // until we know what we're loading. We should probably do this for
+ // all about: pages that we already know the title for so we don't have
+ // to wait for the title to be parsed.
+ if (isAboutBlankPage) {
+ return locale.translation('aboutBlankTitle')
+ } else if (isNewTabPage) {
+ return locale.translation('newTab')
+ }
+
+ // YouTube tries to change the title to add a play icon when
+ // there is audio. Since we have our own audio indicator we get
+ // rid of it.
+ return (frame.get('title') || frame.get('location') || '').replace('▶ ', '')
+ }
diff --git a/app/common/state/tabUIState.js b/app/common/state/tabUIState.js
new file mode 100644
index 00000000000..772c014a584
--- /dev/null
+++ b/app/common/state/tabUIState.js
@@ -0,0 +1,160 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Constants
+const settings = require('../../../js/constants/settings')
+
+// State helpers
+const partitionState = require('../../common/state/tabContentState/partitionState')
+const privateState = require('../../common/state/tabContentState/privateState')
+const closeState = require('../../common/state/tabContentState/closeState')
+const frameStateUtil = require('../../../js/state/frameStateUtil')
+
+// Utils
+const {isEntryIntersected} = require('../../../app/renderer/lib/observerUtil')
+const {getTextColorForBackground} = require('../../../js/lib/color')
+
+// Settings
+const {getSetting} = require('../../../js/settings')
+
+// Styles
+const {intersection} = require('../../renderer/components/styles/global')
+const {theme} = require('../../renderer/components/styles/theme')
+
+module.exports.getThemeColor = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ if (process.env.NODE_ENV !== 'test') {
+ console.error('Unable to find frame for getThemeColor method')
+ }
+ return false
+ }
+
+ return (
+ getSetting(settings.PAINT_TABS) &&
+ (frame.get('themeColor') || frame.get('computedThemeColor'))
+ )
+}
+
+module.exports.getTabIconColor = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ if (process.env.NODE_ENV !== 'test') {
+ console.error('Unable to find frame for getTabIconColor method')
+ }
+ return ''
+ }
+
+ const isPrivate = frame.get('isPrivate')
+ const isActive = frameStateUtil.isFrameKeyActive(state, frameKey)
+ const hoverState = frameStateUtil.getTabHoverState(state, frameKey)
+ const themeColor = frame.get('themeColor') || frame.get('computedThemeColor')
+ const activeNonPrivateTab = !isPrivate && isActive
+ const isPrivateTab = isPrivate && (isActive || hoverState)
+ const defaultColor = isPrivateTab ? 'white' : 'black'
+ const isPaintTabs = getSetting(settings.PAINT_TABS)
+
+ return activeNonPrivateTab && isPaintTabs && !!themeColor
+ ? getTextColorForBackground(themeColor)
+ : defaultColor
+}
+
+module.exports.checkIfTextColor = (state, frameKey, color) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ if (process.env.NODE_ENV !== 'test') {
+ console.error('Unable to find frame for checkIfTextColor method')
+ }
+ return false
+ }
+
+ return module.exports.getTabIconColor(state, frameKey) === color
+}
+
+module.exports.showTabEndIcon = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ if (process.env.NODE_ENV !== 'test') {
+ console.error('Unable to find frame for showTabEndIcon method')
+ }
+ return false
+ }
+
+ return (
+ !closeState.hasFixedCloseIcon(state, frameKey) &&
+ !closeState.hasRelativeCloseIcon(state, frameKey) &&
+ !isEntryIntersected(state, 'tabs', intersection.at40)
+ )
+}
+
+module.exports.addExtraGutterToTitle = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ if (process.env.NODE_ENV !== 'test') {
+ console.error('Unable to find frame for addExtraGutterToTitle method')
+ }
+ return false
+ }
+
+ return frameStateUtil.frameLocationMatch(frame, 'about:newtab')
+}
+
+module.exports.centralizeTabIcons = (state, frameKey, isPinned) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ if (process.env.NODE_ENV !== 'test') {
+ console.error('Unable to find frame for centralizeTabIcons method')
+ }
+ return false
+ }
+
+ return isPinned || isEntryIntersected(state, 'tabs', intersection.at40)
+}
+
+module.exports.getTabEndIconBackgroundColor = (state, frameKey) => {
+ const frame = frameStateUtil.getFrameByKey(state, frameKey)
+
+ if (frame == null) {
+ if (process.env.NODE_ENV !== 'test') {
+ console.error('Unable to find frame for getTabEndIconBackgroundColor method')
+ }
+ return false
+ }
+
+ const themeColor = module.exports.getThemeColor(state, frameKey)
+ const isPrivate = privateState.isPrivateTab(state, frameKey)
+ const isPartition = partitionState.isPartitionTab(state, frameKey)
+ const isHover = frameStateUtil.getTabHoverState(state, frameKey)
+ const isActive = frameStateUtil.isFrameKeyActive(state, frameKey)
+ const hasCloseIcon = closeState.showCloseTabIcon(state, frameKey)
+ const isIntersecting = isEntryIntersected(state, 'tabs', intersection.at40)
+
+ let backgroundColor = theme.tab.background
+
+ if (isActive && themeColor) {
+ backgroundColor = themeColor
+ }
+ if (isActive && !themeColor) {
+ backgroundColor = theme.tab.active.background
+ }
+ if (isIntersecting) {
+ backgroundColor = 'transparent'
+ }
+ if (!isActive && isPrivate) {
+ backgroundColor = theme.tab.private.background
+ }
+ if ((isActive || isHover) && isPrivate) {
+ backgroundColor = theme.tab.active.private.background
+ }
+
+ return isPartition || isPrivate || hasCloseIcon
+ ? `linear-gradient(to left, ${backgroundColor} 10px, transparent 40px)`
+ : `linear-gradient(to left, ${backgroundColor} 0, transparent 12px)`
+}
diff --git a/app/extensions/brave/img/tabs/close_btn.svg b/app/extensions/brave/img/tabs/close_btn.svg
new file mode 100644
index 00000000000..926636ea455
--- /dev/null
+++ b/app/extensions/brave/img/tabs/close_btn.svg
@@ -0,0 +1 @@
+
diff --git a/app/extensions/brave/img/tabs/close_btn_hover.svg b/app/extensions/brave/img/tabs/close_btn_hover.svg
deleted file mode 100644
index 20d2664d403..00000000000
--- a/app/extensions/brave/img/tabs/close_btn_hover.svg
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
diff --git a/app/extensions/brave/img/tabs/close_btn_normal.svg b/app/extensions/brave/img/tabs/close_btn_normal.svg
deleted file mode 100644
index 74a67605ec3..00000000000
--- a/app/extensions/brave/img/tabs/close_btn_normal.svg
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-
diff --git a/app/extensions/brave/img/tabs/default.svg b/app/extensions/brave/img/tabs/default.svg
new file mode 100644
index 00000000000..87b2ba2db9b
--- /dev/null
+++ b/app/extensions/brave/img/tabs/default.svg
@@ -0,0 +1 @@
+
diff --git a/app/renderer/components/styles/animations.js b/app/renderer/components/styles/animations.js
index 87269b944f9..54c421d03aa 100644
--- a/app/renderer/components/styles/animations.js
+++ b/app/renderer/components/styles/animations.js
@@ -20,7 +20,19 @@ const opacityIncreaseKeyframes = {
}
}
+// TODO: this could be a function with param included
+// to which property should be changed
+const widthIncreaseKeyframes = (start, end) => ({
+ 'from': {
+ width: start
+ },
+ 'to': {
+ width: end
+ }
+})
+
module.exports = {
spinKeyframes,
- opacityIncreaseKeyframes
+ opacityIncreaseKeyframes,
+ widthIncreaseKeyframes
}
diff --git a/app/renderer/components/styles/commonStyles.js b/app/renderer/components/styles/commonStyles.js
index 0ffa4b810fa..35bfef0d1de 100644
--- a/app/renderer/components/styles/commonStyles.js
+++ b/app/renderer/components/styles/commonStyles.js
@@ -160,7 +160,7 @@ const styles = StyleSheet.create({
notificationBar__notificationItem: {
backgroundColor: globalStyles.color.notificationItemColor,
boxSizing: 'border-box',
- borderTop: `1px solid ${globalStyles.color.tabsToolbarBorderColor}`,
+ boxShadow: `0 -1px 0 ${globalStyles.color.tabsToolbarBorderColor}`,
lineHeight: '24px',
padding: '8px 20px'
},
diff --git a/app/renderer/components/styles/global.js b/app/renderer/components/styles/global.js
index d6341161e71..0ec4533723a 100644
--- a/app/renderer/components/styles/global.js
+++ b/app/renderer/components/styles/global.js
@@ -1,26 +1,18 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
const {opacityIncreaseKeyframes} = require('./animations')
/**
- * Historically this file includes styles with no defined criteria.
- * Imagine this file as a future reference for theming, in a way
- * that each component should be an object wrapping all properties
- * that would change in a dark mode, for example.
- *
- * Valid as well for things that needs to be fully global,
- * i.e. breakpoints, icons and zIndexes.
- *
- * Thus said, please take preference for inlined styles in the component itself.
- * If you really feel repetitve writing the same style for a given component,
- * consider including a variable inside component.
- *
- * TODO:
- * remove unnecessary styles properties (as items get refactored)
- * Remove fully global items and take preference for component properties (@see button)
- */
+* Use this file when the style you need
+* is applied in more than one element, or depends on it
+* Use theme.js file to include colors that can be customized
+*
+* TODO:
+* remove unnecessary styles properties (as items get refactored)
+* migrate customizable options to theme.js
+*/
const globalStyles = {
defaultFontFamily: `-apple-system, BlinkMacSystemFont, "Segoe UI"` +
@@ -33,17 +25,18 @@ const globalStyles = {
breakpointExtensionButtonPadding: '720px',
breakpointSmallWin32: '650px',
breakpointTinyWin32: '500px',
- breakpointNewPrivateTab: '890px',
- tab: {
- default: '184px', // match tabArea max-width
- large: '120px',
- largeMedium: '83px',
- medium: '66px',
- mediumSmall: '53px',
- small: '46px',
- extraSmall: '40px',
- smallest: '19px'
- }
+ breakpointNewPrivateTab: '890px' // page's breakpoint for the private tab page
+ },
+ intersection: {
+ // whereas 1 === 100%
+ noIntersection: 1,
+ at75: 0.75,
+ at60: 0.6,
+ at45: 0.45,
+ at40: 0.4,
+ at30: 0.3,
+ at20: 0.20,
+ at12: 0.125
},
color: {
commonTextColor: '#3b3b3b',
@@ -126,6 +119,7 @@ const globalStyles = {
carotRadius: '8px'
},
spacing: {
+ sentinelSize: '120px',
navigatorHeight: '48px',
defaultSpacing: '12px',
defaultFontSize: '13px',
@@ -168,9 +162,10 @@ const globalStyles = {
aboutPageDetailsPageWidth: '704px',
aboutPageSectionPadding: '24px',
aboutPageSectionMargin: '10px',
- defaultTabPadding: '0 4px',
+ defaultTabMargin: '6px',
defaultIconPadding: '2px',
iconSize: '16px',
+ sessionIconSize: '15px',
closeIconSize: '13px',
narrowIconSize: '12px',
dialogWidth: '422px',
@@ -219,6 +214,7 @@ const globalStyles = {
zindexWindowIsPreview: '1100',
zindexDownloadsBar: '1000',
zindexTabs: '1000',
+ zindexTabsAudioTopBorder: '1001',
zindexTabsThumbnail: '1100',
zindexTabsDragIndicator: '1100',
zindexNavigationBar: '2000',
diff --git a/app/renderer/components/styles/theme.js b/app/renderer/components/styles/theme.js
new file mode 100644
index 00000000000..00a5b00088e
--- /dev/null
+++ b/app/renderer/components/styles/theme.js
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ /**
+ * Includes color options for theming
+ * This should be used as a boilerplate for all
+ * future theming, including darkUI.
+ * Note: If an element is not color-related, it should go into global.js
+ */
+ module.exports.theme = {
+ tab: {
+ // mimics chrome hover effect
+ transition: `
+ background-color 200ms cubic-bezier(0.26, 0.63, 0.39, 0.65),
+ color 200ms cubic-bezier(0.26, 0.63, 0.39, 0.65)
+ `,
+ background: '#ddd',
+ borderColor: '#bbb',
+ color: '#5a5a5a',
+
+ hover: {
+ background: 'rgba(255, 255, 255, 0.4)'
+ },
+
+ forWindows: {
+ color: '#555'
+ },
+
+ active: {
+ background: 'rgba(255, 255, 255, 0.8)',
+
+ private: {
+ background: '#4b3c6e',
+ color: '#fff'
+ }
+ },
+
+ private: {
+ background: '#d9d6e0',
+ color: '#4b3c6e'
+ },
+
+ content: {
+ icon: {
+ default: {
+ primary: '#fff',
+ secondary: 'rgb(101, 101, 101)'
+ },
+
+ private: {
+ background: {
+ active: '#fff',
+ notActive: '#000'
+ }
+ },
+
+ audio: {
+ color: '#69B9F9'
+ },
+
+ close: {
+ filter: 'invert(100%) grayscale(1) contrast(0.5) brightness(160%)'
+ }
+ }
+ }
+ }
+ }
diff --git a/app/renderer/components/tabs/content/audioTabIcon.js b/app/renderer/components/tabs/content/audioTabIcon.js
index f286d6dc53d..68f891c1adf 100644
--- a/app/renderer/components/tabs/content/audioTabIcon.js
+++ b/app/renderer/components/tabs/content/audioTabIcon.js
@@ -4,24 +4,22 @@
const React = require('react')
const {StyleSheet, css} = require('aphrodite/no-important')
-const Immutable = require('immutable')
// Components
const ReduxComponent = require('../../reduxComponent')
const TabIcon = require('./tabIcon')
-// State
-const tabState = require('../../../../common/state/tabState')
+// State helpers
+const audioState = require('../../../../common/state/tabContentState/audioState')
+const frameStateUtil = require('../../../../../js/state/frameStateUtil')
// Actions
const windowActions = require('../../../../../js/actions/windowActions')
-// Utils
-const frameStateUtil = require('../../../../../js/state/frameStateUtil')
-
// Styles
+const {widthIncreaseKeyframes} = require('../../styles/animations')
const globalStyles = require('../../styles/global')
-const tabStyles = require('../../styles/tab')
+const {theme} = require('../../styles/theme')
class AudioTabIcon extends React.Component {
constructor (props) {
@@ -30,48 +28,70 @@ class AudioTabIcon extends React.Component {
}
get audioIcon () {
- const isNotMuted = this.props.pageCanPlayAudio && !this.props.audioMuted
-
- return isNotMuted
+ return this.props.audioPlaying
? globalStyles.appIcons.volumeOn
: globalStyles.appIcons.volumeOff
}
toggleMute (event) {
event.stopPropagation()
- windowActions.setAudioMuted(this.props.frameKey, this.props.tabId, !this.props.audioMuted)
+ windowActions
+ .setAudioMuted(this.props.frameKey, this.props.tabId, this.props.audioPlaying)
}
mergeProps (state, ownProps) {
const currentWindow = state.get('currentWindow')
-
- // AudioIcon will never be created if there is no frameKey, but for consistency
- // across other components I added teh || Immutable.Map()
- const frame = frameStateUtil.getFrameByKey(currentWindow, ownProps.frameKey) || Immutable.Map()
+ const tabId = ownProps.tabId
+ const frameKey = frameStateUtil.getFrameKeyByTabId(currentWindow, tabId)
const props = {}
- // used in other functions
- props.frameKey = ownProps.frameKey
- props.pageCanPlayAudio = !!frame.get('audioPlaybackActive')
- props.tabId = frame.get('tabId', tabState.TAB_ID_NONE)
- props.audioMuted = frame.get('audioMuted')
+ props.frameKey = frameKey
+ props.showAudioIcon = audioState.showAudioIcon(currentWindow, frameKey)
+ props.audioPlaying = !audioState.isAudioMuted(currentWindow, frameKey)
+ props.canPlayAudio = audioState.canPlayAudio(currentWindow, frameKey)
+ props.isPinned = frameStateUtil.isPinned(currentWindow, frameKey)
+ props.tabId = tabId
return props
}
render () {
+ if (this.props.isPinned || !this.props.showAudioIcon) {
+ return null
+ }
+
return
}
}
-module.exports = ReduxComponent.connect(AudioTabIcon)
-
const styles = StyleSheet.create({
- audioIcon: {
- color: globalStyles.color.highlightBlue,
- fontSize: '16px'
+ audioTab__icon: {
+ width: 0,
+ animationName: widthIncreaseKeyframes(0, globalStyles.spacing.iconSize),
+ animationDelay: '50ms',
+ animationTimingFunction: 'linear',
+ animationDuration: '100ms',
+ animationFillMode: 'forwards',
+
+ overflow: 'hidden',
+ margin: '0 -2px 0 2px',
+ zIndex: globalStyles.zindex.zindexTabsAudioTopBorder,
+ color: theme.tab.content.icon.audio.color,
+ fontSize: '13px',
+ height: globalStyles.spacing.iconSize,
+ backgroundSize: globalStyles.spacing.iconSize,
+ backgroundPosition: 'center',
+ backgroundRepeat: 'no-repeat',
+ display: 'flex',
+ alignSelf: 'center',
+ position: 'relative',
+ textAlign: 'center',
+ justifyContent: 'center'
}
})
+
+module.exports = ReduxComponent.connect(AudioTabIcon)
diff --git a/app/renderer/components/tabs/content/closeTabIcon.js b/app/renderer/components/tabs/content/closeTabIcon.js
index 577350c99cc..43f0973e31a 100644
--- a/app/renderer/components/tabs/content/closeTabIcon.js
+++ b/app/renderer/components/tabs/content/closeTabIcon.js
@@ -1,30 +1,28 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
const React = require('react')
const {StyleSheet, css} = require('aphrodite/no-important')
-const Immutable = require('immutable')
// Components
const ReduxComponent = require('../../reduxComponent')
const TabIcon = require('./tabIcon')
-// State
-const tabContentState = require('../../../../common/state/tabContentState')
-const tabState = require('../../../../common/state/tabState')
+// State helpers
+const tabUIState = require('../../../../common/state/tabUIState')
+const closeState = require('../../../../common/state/tabContentState/closeState')
+const frameStateUtil = require('../../../../../js/state/frameStateUtil')
// Actions
const windowActions = require('../../../../../js/actions/windowActions')
const appActions = require('../../../../../js/actions/appActions')
-// Utils
-const frameStateUtil = require('../../../../../js/state/frameStateUtil')
-
// Styles
-const globalStyles = require('../../styles/global')
-const closeTabHoverSvg = require('../../../../extensions/brave/img/tabs/close_btn_hover.svg')
-const closeTabSvg = require('../../../../extensions/brave/img/tabs/close_btn_normal.svg')
+const {theme} = require('../../styles/theme')
+const {spacing, zindex} = require('../../styles/global')
+const {opacityIncreaseKeyframes} = require('../../styles/animations')
+const closeTabSvg = require('../../../../extensions/brave/img/tabs/close_btn.svg')
class CloseTabIcon extends React.Component {
constructor (props) {
@@ -49,32 +47,33 @@ class CloseTabIcon extends React.Component {
mergeProps (state, ownProps) {
const currentWindow = state.get('currentWindow')
- const frameKey = ownProps.frameKey
- const isPinnedTab = frameStateUtil.isPinned(currentWindow, frameKey)
- const frame = frameStateUtil.getFrameByKey(currentWindow, frameKey) || Immutable.Map()
+ const tabId = ownProps.tabId
+ const frameKey = frameStateUtil.getFrameKeyByTabId(currentWindow, tabId)
+ const isPinned = frameStateUtil.isPinned(currentWindow, frameKey)
const props = {}
- // used in renderer
- props.showCloseIcon = !isPinnedTab &&
- (
- tabContentState.hasRelativeCloseIcon(currentWindow, frameKey) ||
- tabContentState.hasFixedCloseIcon(currentWindow, frameKey)
- )
-
- // used in functions
- props.frameKey = frameKey
+ props.isPinned = isPinned
props.fixTabWidth = ownProps.fixTabWidth
- props.tabId = frame.get('tabId', tabState.TAB_ID_NONE)
- props.hasFrame = !frame.isEmpty()
+ props.hasFrame = frameStateUtil.hasFrame(currentWindow, frameKey)
+ props.centralizeTabIcons = tabUIState.centralizeTabIcons(currentWindow, frameKey, isPinned)
+ props.showCloseIcon = closeState.showCloseTabIcon(currentWindow, frameKey)
+ props.tabId = tabId
return props
}
render () {
+ if (this.props.isPinned || !this.props.showCloseIcon) {
+ return null
+ }
+
return
}
}
@@ -79,20 +97,53 @@ class Favicon extends React.Component {
module.exports = ReduxComponent.connect(Favicon)
const styles = StyleSheet.create({
- faviconNarrowView: {
- minWidth: 'auto',
- width: globalStyles.spacing.narrowIconSize,
- backgroundSize: 'contain',
- padding: 0,
- fontSize: '10px',
- backgroundPosition: 'center center'
+ icon: {
+ opacity: 0,
+ willChange: 'opacity',
+ animationName: opacityIncreaseKeyframes,
+ animationDelay: '50ms',
+ animationTimingFunction: 'linear',
+ animationDuration: '200ms',
+ animationFillMode: 'forwards',
+
+ position: 'relative',
+ boxSizing: 'border-box',
+ width: spacing.iconSize,
+ height: spacing.iconSize,
+ backgroundSize: spacing.iconSize,
+ backgroundPosition: 'center',
+ backgroundRepeat: 'no-repeat',
+ display: 'flex',
+ alignSelf: 'center'
+ },
+
+ icon_lessMargin: {
+ margin: 0
},
- loadingIcon: {
+ icon_reducedSize: {
+ width: spacing.narrowIconSize,
+ height: '-webkit-fill-available',
+ alignItems: 'center',
+ backgroundSize: spacing.narrowIconSize
+ },
+
+ icon__loading: {
+ position: 'absolute',
+ left: 0,
+ willChange: 'transform',
backgroundImage: `url(${loadingIconSvg})`,
+ backgroundRepeat: 'no-repeat',
+ backgroundPosition: 'top left',
animationName: spinKeyframes,
animationTimingFunction: 'linear',
animationDuration: '1200ms',
animationIterationCount: 'infinite'
+ },
+
+ icon__default: {
+ WebkitMaskRepeat: 'no-repeat',
+ WebkitMaskPosition: 'center',
+ WebkitMaskImage: `url(${defaultIconSvg})`
}
})
diff --git a/app/renderer/components/tabs/content/newSessionIcon.js b/app/renderer/components/tabs/content/newSessionIcon.js
index 443da11024a..47533c08552 100644
--- a/app/renderer/components/tabs/content/newSessionIcon.js
+++ b/app/renderer/components/tabs/content/newSessionIcon.js
@@ -1,65 +1,63 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
const React = require('react')
const {StyleSheet, css} = require('aphrodite/no-important')
-const Immutable = require('immutable')
// Components
const ReduxComponent = require('../../reduxComponent')
const TabIcon = require('./tabIcon')
// State
-const tabContentState = require('../../../../common/state/tabContentState')
-
-// Constants
-const {tabs} = require('../../../../../js/constants/config')
-
-// Utils
+const partitionState = require('../../../../common/state/tabContentState/partitionState')
+const tabUIState = require('../../../../common/state/tabUIState')
const frameStateUtil = require('../../../../../js/state/frameStateUtil')
// Styles
-const tabStyles = require('../../styles/tab')
+const globalStyles = require('../../styles/global')
+const {opacityIncreaseKeyframes} = require('../../styles/animations')
const newSessionSvg = require('../../../../extensions/brave/img/tabs/new_session.svg')
class NewSessionIcon extends React.Component {
mergeProps (state, ownProps) {
const currentWindow = state.get('currentWindow')
- const frameKey = ownProps.frameKey
- const frame = frameStateUtil.getFrameByKey(currentWindow, frameKey) || Immutable.Map()
- const partition = frame.get('partitionNumber')
+ const tabId = ownProps.tabId
+ const frameKey = frameStateUtil.getFrameKeyByTabId(currentWindow, tabId)
const props = {}
- // used in renderer
+ props.isPinned = frameStateUtil.isPinned(currentWindow, frameKey)
+ props.showPartitionIcon = partitionState.showPartitionIcon(currentWindow, frameKey)
props.isActive = frameStateUtil.isFrameKeyActive(currentWindow, frameKey)
- props.iconColor = tabContentState.getTabIconColor(currentWindow, frameKey)
- props.partitionNumber = typeof partition === 'string'
- ? partition.replace(/^partition-/i, '')
- : partition
- props.partitionIndicator = props.partitionNumber > tabs.maxAllowedNewSessions
- ? tabs.maxAllowedNewSessions
- : props.partitionNumber
-
- // used in functions
- props.frameKey = frameKey
+ props.textIsWhite = tabUIState.checkIfTextColor(currentWindow, frameKey, 'white')
+ props.partitionNumber = partitionState.getMaxAllowedPartitionNumber(currentWindow, frameKey)
+ props.tabId = tabId
return props
}
render () {
- const newSession = StyleSheet.create({
- indicator: {
- // Based on getTextColorForBackground() icons can be only black or white.
- filter: this.props.isActive && this.props.iconColor === 'white' ? 'invert(100%)' : 'none'
+ if (
+ this.props.isPinned ||
+ !this.props.showPartitionIcon ||
+ this.props.partitionNumber === 0
+ ) {
+ return null
+ }
+
+ const newSessionProps = StyleSheet.create({
+ newSession__indicator: {
+ filter: this.props.isActive && this.props.textIsWhite
+ ? 'invert(100%)'
+ : 'none'
}
})
return
}
@@ -68,9 +66,25 @@ class NewSessionIcon extends React.Component {
module.exports = ReduxComponent.connect(NewSessionIcon)
const styles = StyleSheet.create({
- newSession: {
- position: 'relative',
+ newSession__icon: {
+ opacity: 0,
+ willChange: 'opacity',
+ animationName: opacityIncreaseKeyframes,
+ animationDelay: '100ms',
+ animationTimingFunction: 'linear',
+ animationDuration: '200ms',
+ animationFillMode: 'forwards',
+
+ zIndex: globalStyles.zindex.zindexTabsThumbnail,
+ boxSizing: 'border-box',
+ display: 'flex',
+ alignItems: 'center',
backgroundImage: `url(${newSessionSvg})`,
- backgroundPosition: 'left'
+ backgroundPosition: 'center left',
+ backgroundRepeat: 'no-repeat',
+ backgroundSize: '13px',
+ width: globalStyles.spacing.iconSize,
+ height: globalStyles.spacing.iconSize,
+ marginRight: globalStyles.spacing.defaultTabMargin
}
})
diff --git a/app/renderer/components/tabs/content/privateIcon.js b/app/renderer/components/tabs/content/privateIcon.js
index 2f3f3c3225e..92838736b18 100644
--- a/app/renderer/components/tabs/content/privateIcon.js
+++ b/app/renderer/components/tabs/content/privateIcon.js
@@ -1,6 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
const React = require('react')
const {StyleSheet, css} = require('aphrodite/no-important')
@@ -9,39 +9,47 @@ const {StyleSheet, css} = require('aphrodite/no-important')
const ReduxComponent = require('../../reduxComponent')
const TabIcon = require('./tabIcon')
-// Utils
+// State helpers
+const privateState = require('../../../../common/state/tabContentState/privateState')
const frameStateUtil = require('../../../../../js/state/frameStateUtil')
// Styles
+const {theme} = require('../../styles/theme')
const globalStyles = require('../../styles/global')
-const tabStyles = require('../../styles/tab')
+const {opacityIncreaseKeyframes} = require('../../styles/animations')
const privateSvg = require('../../../../extensions/brave/img/tabs/private.svg')
class PrivateIcon extends React.Component {
mergeProps (state, ownProps) {
const currentWindow = state.get('currentWindow')
- const frameKey = ownProps.frameKey
+ const tabId = ownProps.tabId
+ const frameKey = frameStateUtil.getFrameKeyByTabId(currentWindow, tabId)
const props = {}
- // used in renderer
+ props.isPinned = frameStateUtil.isPinned(currentWindow, frameKey)
props.isActive = frameStateUtil.isFrameKeyActive(currentWindow, frameKey)
-
- // used in functions
- props.frameKey = frameKey
+ props.showPrivateIcon = privateState.showPrivateIcon(currentWindow, frameKey)
+ props.tabId = tabId
return props
}
render () {
- const privateStyles = StyleSheet.create({
- icon: {
- backgroundColor: this.props.isActive ? globalStyles.color.white100 : globalStyles.color.black100
+ if (this.props.isPinned || !this.props.showPrivateIcon) {
+ return null
+ }
+
+ const privateProps = StyleSheet.create({
+ private__icon_color: {
+ backgroundColor: this.props.isActive
+ ? theme.tab.content.icon.private.background.active
+ : theme.tab.content.icon.private.background.notActive
}
})
return
}
}
@@ -49,9 +57,23 @@ class PrivateIcon extends React.Component {
module.exports = ReduxComponent.connect(PrivateIcon)
const styles = StyleSheet.create({
- secondaryIcon: {
+ private__icon: {
+ opacity: 0,
+ willChange: 'opacity',
+ animationName: opacityIncreaseKeyframes,
+ animationDelay: '100ms',
+ animationTimingFunction: 'linear',
+ animationDuration: '200ms',
+ animationFillMode: 'forwards',
+
+ zIndex: globalStyles.zindex.zindexTabsThumbnail,
+ boxSizing: 'border-box',
WebkitMaskRepeat: 'no-repeat',
WebkitMaskPosition: 'center',
- WebkitMaskImage: `url(${privateSvg})`
+ WebkitMaskImage: `url(${privateSvg})`,
+ WebkitMaskSize: globalStyles.spacing.sessionIconSize,
+ width: globalStyles.spacing.sessionIconSize,
+ height: globalStyles.spacing.sessionIconSize,
+ marginRight: globalStyles.spacing.defaultTabMargin
}
})
diff --git a/app/renderer/components/tabs/content/tabIcon.js b/app/renderer/components/tabs/content/tabIcon.js
index 8f8c32fabde..b98851ae97a 100644
--- a/app/renderer/components/tabs/content/tabIcon.js
+++ b/app/renderer/components/tabs/content/tabIcon.js
@@ -24,7 +24,7 @@ class TabIcon extends ImmutableComponent {
width: globalStyles.spacing.iconSize,
height: globalStyles.spacing.iconSize,
alignItems: 'center',
- justifyContent: this.props.symbolContent ? 'flex-end' : 'left',
+ justifyContent: this.props.symbolContent ? 'flex-end' : 'center',
fontWeight: this.props.symbolContent ? 'bold' : 'normal',
color: this.props.symbolContent ? globalStyles.color.black100 : 'inherit'
}
diff --git a/app/renderer/components/tabs/content/tabTitle.js b/app/renderer/components/tabs/content/tabTitle.js
index b57e414512a..4c6be4a3c18 100644
--- a/app/renderer/components/tabs/content/tabTitle.js
+++ b/app/renderer/components/tabs/content/tabTitle.js
@@ -1,6 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
const React = require('react')
const {StyleSheet, css} = require('aphrodite/no-important')
@@ -8,8 +8,10 @@ const {StyleSheet, css} = require('aphrodite/no-important')
// Components
const ReduxComponent = require('../../reduxComponent')
-// State
-const tabContentState = require('../../../../common/state/tabContentState')
+// State helpers
+const titleState = require('../../../../common/state/tabContentState/titleState')
+const frameStateUtil = require('../../../../../js/state/frameStateUtil')
+const tabUIState = require('../../../../common/state/tabUIState')
// Utils
const platformUtil = require('../../../../common/lib/platformUtil')
@@ -22,36 +24,34 @@ const globalStyles = require('../../styles/global')
class TabTitle extends React.Component {
mergeProps (state, ownProps) {
const currentWindow = state.get('currentWindow')
- const frameKey = ownProps.frameKey
- const tabIconColor = tabContentState.getTabIconColor(currentWindow, frameKey)
+ const tabId = ownProps.tabId
+ const frameKey = frameStateUtil.getFrameKeyByTabId(currentWindow, tabId)
const props = {}
- // used in renderer
- props.enforceFontVisibility = isDarwin && tabIconColor === 'white'
- props.tabIconColor = tabIconColor
- props.displayTitle = tabContentState.getDisplayTitle(currentWindow, frameKey)
-
- // used in functions
- props.frameKey = frameKey
+ props.isWindows = isWindows
+ props.isDarwin = isDarwin
+ props.isPinned = frameStateUtil.isPinned(currentWindow, frameKey)
+ props.showTabTitle = titleState.showTabTitle(currentWindow, frameKey)
+ props.displayTitle = titleState.getDisplayTitle(currentWindow, frameKey)
+ props.addExtraGutter = tabUIState.addExtraGutterToTitle(currentWindow, frameKey)
+ props.isTextWhite = tabUIState.checkIfTextColor(currentWindow, frameKey, 'white')
+ props.tabId = tabId
return props
}
render () {
- const titleStyles = StyleSheet.create({
- gradientText: {
- backgroundImage: `-webkit-linear-gradient(left,
- ${this.props.tabIconColor} 90%, ${globalStyles.color.almostInvisible} 100%)`
- }
- })
+ if (this.props.isPinned || !this.props.showTabTitle) {
+ return null
+ }
return
{this.props.displayTitle}
@@ -61,28 +61,29 @@ class TabTitle extends React.Component {
module.exports = ReduxComponent.connect(TabTitle)
const styles = StyleSheet.create({
- tabTitle: {
+
+ tab__title: {
+ boxSizing: 'border-box',
display: 'flex',
- flex: '1',
+ flex: 1,
userSelect: 'none',
- boxSizing: 'border-box',
fontSize: globalStyles.fontSize.tabTitle,
- overflow: 'hidden',
- whiteSpace: 'nowrap',
lineHeight: '1.6',
- padding: globalStyles.spacing.defaultTabPadding,
- color: 'transparent',
- WebkitBackgroundClip: 'text',
- // prevents the title from being the target of mouse events.
- pointerEvents: 'none'
+ minWidth: 0, // see https://stackoverflow.com/a/36247448/4902448
+ marginLeft: '4px',
+ overflow: 'hidden'
},
- enforceFontVisibility: {
- fontWeight: '600'
+ tab__title_isDarwin: {
+ fontWeight: '400'
},
- tabTitleForWindows: {
+ tab__title_isWindows: {
fontWeight: '500',
fontSize: globalStyles.fontSize.tabTitle
+ },
+
+ tab__title_extraGutter: {
+ margin: '0 2px'
}
})
diff --git a/app/renderer/components/tabs/tab.js b/app/renderer/components/tabs/tab.js
index 7be5585f9d6..2ff194c2bdc 100644
--- a/app/renderer/components/tabs/tab.js
+++ b/app/renderer/components/tabs/tab.js
@@ -1,9 +1,9 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
const React = require('react')
-const {StyleSheet, css} = require('aphrodite')
+const {StyleSheet, css} = require('aphrodite/no-important')
const Immutable = require('immutable')
// Components
@@ -23,15 +23,18 @@ const windowActions = require('../../../../js/actions/windowActions')
// Store
const windowStore = require('../../../../js/stores/windowStore')
-// State
-const tabContentState = require('../../../common/state/tabContentState')
+// State helpers
+const privateState = require('../../../common/state/tabContentState/privateState')
+const audioState = require('../../../common/state/tabContentState/audioState')
+const tabUIState = require('../../../common/state/tabUIState')
const tabState = require('../../../common/state/tabState')
// Constants
const dragTypes = require('../../../../js/constants/dragTypes')
// Styles
-const styles = require('../styles/tab')
+const globalStyles = require('../styles/global')
+const {theme} = require('../styles/theme')
// Utils
const cx = require('../../../../js/lib/classSet')
@@ -39,16 +42,11 @@ const {getTextColorForBackground} = require('../../../../js/lib/color')
const {isIntermediateAboutPage} = require('../../../../js/lib/appUrlUtil')
const contextMenus = require('../../../../js/contextMenus')
const dnd = require('../../../../js/dnd')
-const throttle = require('../../../../js/lib/throttle')
const frameStateUtil = require('../../../../js/state/frameStateUtil')
-const {
- getTabBreakpoint,
- tabUpdateFrameRate,
- hasBreakpoint,
- hasTabAsRelatedTarget
-} = require('../../lib/tabUtil')
+const {hasTabAsRelatedTarget} = require('../../lib/tabUtil')
const isWindows = require('../../../common/lib/platformUtil').isWindows()
const {getCurrentWindowId} = require('../../currentWindow')
+const {setObserver} = require('../../lib/observerUtil')
const UrlUtil = require('../../../../js/lib/urlutil')
class Tab extends React.Component {
@@ -57,11 +55,12 @@ class Tab extends React.Component {
this.onMouseMove = this.onMouseMove.bind(this)
this.onMouseEnter = this.onMouseEnter.bind(this)
this.onMouseLeave = this.onMouseLeave.bind(this)
- this.onUpdateTabSize = this.onUpdateTabSize.bind(this)
+ this.onDrag = this.onDrag.bind(this)
this.onDragStart = this.onDragStart.bind(this)
this.onDragEnd = this.onDragEnd.bind(this)
this.onDragOver = this.onDragOver.bind(this)
this.onClickTab = this.onClickTab.bind(this)
+ this.onObserve = this.onObserve.bind(this)
this.tabNode = null
}
@@ -123,7 +122,19 @@ class Tab extends React.Component {
}
onDragStart (e) {
+ // showing up the sentinel while dragging leads to show the shadow
+ // of the next tab. See 10691#issuecomment-329854096
+ // this is added back to original size when onDrag event is happening
+ this.tabSentinel.style.width = 0
+
dnd.onDragStart(dragTypes.TAB, this.frame, e)
+ // cancel tab preview while dragging. see #10103
+ windowActions.setTabHoverState(this.props.frameKey, false, false)
+ }
+
+ onDrag () {
+ // re-enable the tabSentinel while dragging
+ this.tabSentinel.style.width = globalStyles.spacing.sentinelSize
}
onDragEnd (e) {
@@ -189,34 +200,34 @@ class Tab extends React.Component {
}
}
- get tabSize () {
- const tab = this.tabNode
- // Avoid TypeError keeping it null until component is mounted
- return tab && !this.props.isPinnedTab ? tab.getBoundingClientRect().width : null
- }
-
- onUpdateTabSize () {
- const currentSize = getTabBreakpoint(this.tabSize)
- // Avoid updating breakpoint when user enters fullscreen (see #7301)
- // Also there can be a race condition for pinned tabs if we update when not needed
- // since a new tab component with the same key gets created which is not pinned.
- if (this.props.breakpoint !== currentSize && !this.props.hasTabInFullScreen) {
- windowActions.setTabBreakpoint(this.props.frameKey, currentSize)
+ componentDidMount () {
+ // unobserve tabs that we don't need. This will
+ // likely be made by onObserve method but added again as
+ // just to double-check
+ if (this.props.isPinned) {
+ this.observer && this.observer.unobserve(this.tabSentinel)
}
- }
+ const threshold = Object.values(globalStyles.intersection)
+ // At this moment Chrome can't handle unitless zeroes for rootMargin
+ // see https://github.com/w3c/IntersectionObserver/issues/244
+ const margin = '0px'
+ this.observer = setObserver(this.tabSentinel, threshold, margin, this.onObserve)
+ this.observer.observe(this.tabSentinel)
- componentDidMount () {
- this.onUpdateTabSize()
this.tabNode.addEventListener('auxclick', this.onAuxClick.bind(this))
- window.addEventListener('resize', throttle(this.onUpdateTabSize, tabUpdateFrameRate), { passive: true })
}
- componentDidUpdate () {
- this.onUpdateTabSize()
+ componentWillUnmount () {
+ this.observer.unobserve(this.tabSentinel)
}
- componentWillUnmount () {
- window.removeEventListener('resize', this.onUpdateTabSize)
+ onObserve (entries) {
+ if (this.props.isPinnedTab) {
+ return
+ }
+ // we only have one entry
+ const entry = entries[0]
+ windowActions.setTabIntersectionState(this.props.frameKey, entry.intersectionRatio)
}
get fixTabWidth () {
@@ -231,47 +242,35 @@ class Tab extends React.Component {
mergeProps (state, ownProps) {
const currentWindow = state.get('currentWindow')
const frame = frameStateUtil.getFrameByKey(currentWindow, ownProps.frameKey) || Immutable.Map()
+ const frameKey = ownProps.frameKey
+ const tabId = frame.get('tabId', tabState.TAB_ID_NONE)
+ const isPinned = frameStateUtil.isPinned(currentWindow, frameKey)
+ const partOfFullPageSet = ownProps.partOfFullPageSet
+
+ // TODO: this should have its own method
const notifications = state.get('notifications')
const notificationOrigins = notifications ? notifications.map(bar => bar.get('frameOrigin')) : false
const notificationBarActive = frame.get('location') && notificationOrigins &&
notificationOrigins.includes(UrlUtil.getUrlOrigin(frame.get('location')))
- const hasSeconardImage = tabContentState.hasVisibleSecondaryIcon(currentWindow, ownProps.frameKey)
- const breakpoint = frame.get('breakpoint')
- const partition = typeof frame.get('partitionNumber') === 'string'
- ? frame.get('partitionNumber').replace(/^partition-/i, '')
- : frame.get('partitionNumber')
const props = {}
- // used in renderer
- props.frameKey = ownProps.frameKey
- props.isPrivateTab = frame.get('isPrivate')
- props.breakpoint = frame.get('breakpoint')
+ // TODO: this should have its own method
props.notificationBarActive = notificationBarActive
- props.isActive = frameStateUtil.isFrameKeyActive(currentWindow, props.frameKey)
+ props.frameKey = frameKey
+ props.isPinnedTab = isPinned
+ props.isPrivateTab = privateState.isPrivateTab(currentWindow, frameKey)
+ props.isActive = frameStateUtil.isFrameKeyActive(currentWindow, frameKey)
props.tabWidth = currentWindow.getIn(['ui', 'tabs', 'fixTabWidth'])
- props.isPinnedTab = frameStateUtil.isPinned(currentWindow, props.frameKey)
- props.canPlayAudio = tabContentState.canPlayAudio(currentWindow, props.frameKey)
- props.themeColor = tabContentState.getThemeColor(currentWindow, props.frameKey)
- props.isNarrowView = tabContentState.isNarrowView(currentWindow, props.frameKey)
- props.isNarrowestView = tabContentState.isNarrowestView(currentWindow, props.frameKey)
- props.isPlayIndicatorBreakpoint = tabContentState.isMediumView(currentWindow, props.frameKey) || props.isNarrowView
+ props.themeColor = tabUIState.getThemeColor(currentWindow, frameKey)
props.title = frame.get('title')
- props.showSessionIcon = partition && hasSeconardImage
- props.showPrivateIcon = props.isPrivateTab && hasSeconardImage
- props.showFavIcon = !((hasBreakpoint(breakpoint, 'extraSmall') && props.isActive) || frame.get('location') === 'about:newtab')
- props.showAudioIcon = breakpoint === 'default' && !!frame.get('audioPlaybackActive')
- props.partOfFullPageSet = ownProps.partOfFullPageSet
- props.showTitle = !props.isPinnedTab &&
- !(
- (hasBreakpoint(breakpoint, ['mediumSmall', 'small']) && props.isActive) ||
- hasBreakpoint(breakpoint, ['extraSmall', 'smallest'])
- )
+ props.partOfFullPageSet = partOfFullPageSet
+ props.showAudioTopBorder = audioState.showAudioTopBorder(currentWindow, frameKey, isPinned)
+ props.centralizeTabIcons = tabUIState.centralizeTabIcons(currentWindow, frameKey, isPinned)
+ props.gradientColor = tabUIState.getTabEndIconBackgroundColor(currentWindow, frameKey)
// used in other functions
- props.totalTabs = state.get('tabs').size
props.dragData = state.getIn(['dragData', 'type']) === dragTypes.TAB && state.get('dragData')
- props.hasTabInFullScreen = tabContentState.hasTabInFullScreen(currentWindow)
- props.tabId = frame.get('tabId', tabState.TAB_ID_NONE)
+ props.tabId = tabId
props.previewMode = currentWindow.getIn(['ui', 'tabs', 'previewMode'])
return props
@@ -280,7 +279,7 @@ class Tab extends React.Component {
render () {
// we don't want themeColor if tab is private
const perPageStyles = !this.props.isPrivateTab && StyleSheet.create({
- themeColor: {
+ tab_themeColor: {
color: this.props.themeColor ? getTextColorForBackground(this.props.themeColor) : 'inherit',
background: this.props.themeColor ? this.props.themeColor : 'inherit',
':hover': {
@@ -289,6 +288,13 @@ class Tab extends React.Component {
}
}
})
+ const perPageGradient = StyleSheet.create({
+ tab_gradient: {
+ '::after': {
+ background: this.props.gradientColor
+ }
+ }
+ })
return { this.tabNode = node }}
className={css(
styles.tab,
+ !this.props.isPinnedTab && perPageGradient.tab_gradient,
// Windows specific style
- isWindows && styles.tabForWindows,
- this.props.isPinnedTab && styles.isPinned,
- this.props.isActive && styles.active,
- this.props.isPlayIndicatorBreakpoint && this.props.canPlayAudio && styles.narrowViewPlayIndicator,
- this.props.isActive && this.props.themeColor && perPageStyles.themeColor,
+ isWindows && styles.tab_forWindows,
+ this.props.isPinnedTab && styles.tab_pinned,
+ this.props.isActive && styles.tab_active,
+ this.props.isActive && this.props.themeColor && perPageStyles.tab_themeColor,
+ this.props.showAudioTopBorder && styles.tab_audioTopBorder,
// Private color should override themeColor
- this.props.isPrivateTab && styles.private,
- this.props.isActive && this.props.isPrivateTab && styles.activePrivateTab,
- !this.props.isPinnedTab && this.props.isNarrowView && styles.tabNarrowView,
- !this.props.isPinnedTab && this.props.isNarrowestView && styles.tabNarrowestView,
- !this.props.isPinnedTab && this.props.breakpoint === 'smallest' && styles.tabMinAllowedSize
+ this.props.isPrivateTab && styles.tab_private,
+ this.props.isActive && this.props.isPrivateTab && styles.tab_active_private,
+ this.props.centralizeTabIcons && styles.tab__content_centered
)}
data-test-id='tab'
- data-test-active-tab={this.props.isActive}
- data-test-pinned-tab={this.props.isPinnedTab}
- data-test-private-tab={this.props.isPrivateTab}
data-frame-key={this.props.frameKey}
draggable
title={this.props.title}
+ onDrag={this.onDrag}
onDragStart={this.onDragStart}
onDragEnd={this.onDragEnd}
onDragOver={this.onDragOver}
onClick={this.onClickTab}
onContextMenu={contextMenus.onTabContextMenu.bind(this, this.frame)}
>
+
{ this.tabSentinel = node }}
+ className={css(styles.tab__sentinel)}
+ />
- {
- this.props.showFavIcon
- ?
- : null
- }
- {
- this.props.showAudioIcon
- ?
- : null
- }
- {
- this.props.showTitle
- ?
- : null
- }
+ styles.tab__identity,
+ this.props.centralizeTabIcons && styles.tab__content_centered
+ )}>
+
+
+
- {
- this.props.showPrivateIcon
- ?
- : null
- }
- {
- this.props.showSessionIcon
- ?
- : null
- }
-
+
+
+
}
}
+const styles = StyleSheet.create({
+ tab: {
+ borderWidth: '0 1px 0 0',
+ borderStyle: 'solid',
+ borderColor: '#bbb',
+ boxSizing: 'border-box',
+ color: theme.tab.color,
+ display: 'flex',
+ transition: theme.tab.transition,
+ height: '-webkit-fill-available',
+ width: '-webkit-fill-available',
+ alignItems: 'center',
+ justifyContent: 'space-between',
+ position: 'relative',
+
+ ':hover': {
+ background: theme.tab.hover.background
+ },
+
+ // this enable us to have gradient text
+ '::after': {
+ zIndex: globalStyles.zindex.zindexTabs,
+ content: '""',
+ position: 'absolute',
+ bottom: 0,
+ right: 0,
+ width: '-webkit-fill-available',
+ height: '-webkit-fill-available'
+ }
+ },
+
+ // Windows specific style
+ tab_forWindows: {
+ color: theme.tab.forWindows.color
+ },
+
+ tab_pinned: {
+ padding: 0,
+ width: '28px',
+ justifyContent: 'center'
+ },
+
+ tab_active: {
+ background: theme.tab.active.background,
+
+ ':hover': {
+ background: theme.tab.active.background
+ }
+ },
+
+ tab_audioTopBorder: {
+ '::before': {
+ zIndex: globalStyles.zindex.zindexTabsAudioTopBorder,
+ content: `''`,
+ display: 'block',
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ height: '2px',
+ background: 'lightskyblue'
+ }
+ },
+
+ // The sentinel is responsible to respond to tabs
+ // intersection state. This is an empty hidden element
+ // which `width` value shouldn't be changed unless the intersection
+ // point needs to be edited.
+ tab__sentinel: {
+ position: 'absolute',
+ left: 0,
+ height: '1px',
+ background: 'transparent',
+ width: globalStyles.spacing.sentinelSize
+ },
+
+ tab__identity: {
+ justifyContent: 'flex-start',
+ alignItems: 'center',
+ overflow: 'hidden',
+ display: 'flex',
+ flex: '1',
+ minWidth: '0', // @see https://bugzilla.mozilla.org/show_bug.cgi?id=1108514#c5
+ margin: `0 ${globalStyles.spacing.defaultTabMargin}`
+ },
+
+ tab__content_centered: {
+ justifyContent: 'center',
+ flex: 'auto',
+ padding: 0,
+ margin: 0
+ },
+
+ tab_active_private: {
+ background: theme.tab.active.private.background,
+ color: theme.tab.active.private.color,
+
+ ':hover': {
+ background: theme.tab.active.private.background
+ }
+ },
+
+ tab_private: {
+ background: theme.tab.private.background,
+
+ ':hover': {
+ color: theme.tab.active.private.color,
+ background: theme.tab.active.private.background
+ }
+ }
+})
+
module.exports = ReduxComponent.connect(Tab)
diff --git a/app/renderer/components/tabs/tabs.js b/app/renderer/components/tabs/tabs.js
index dce931ee2da..df8a54b2e14 100644
--- a/app/renderer/components/tabs/tabs.js
+++ b/app/renderer/components/tabs/tabs.js
@@ -17,7 +17,6 @@ const windowActions = require('../../../../js/actions/windowActions')
// State
const windowState = require('../../../common/state/windowState')
-const tabContentState = require('../../../common/state/tabContentState')
// Constants
const dragTypes = require('../../../../js/constants/dragTypes')
@@ -129,7 +128,7 @@ class Tabs extends React.Component {
mergeProps (state, ownProps) {
const currentWindow = state.get('currentWindow')
- const pageIndex = tabContentState.getPageIndex(currentWindow)
+ const pageIndex = frameStateUtil.getTabPageIndex(currentWindow)
const tabsPerTabPage = Number(getSetting(settings.TABS_PER_PAGE))
const startingFrameIndex = pageIndex * tabsPerTabPage
const unpinnedTabs = frameStateUtil.getNonPinnedFrames(currentWindow) || Immutable.List()
diff --git a/app/renderer/lib/observerUtil.js b/app/renderer/lib/observerUtil.js
new file mode 100644
index 00000000000..1ea48445000
--- /dev/null
+++ b/app/renderer/lib/observerUtil.js
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ const {noIntersection} = require('../../renderer/components/styles/global').intersection
+
+ /**
+ * Observes an element's against its parentNode intercection.
+ * This method enable a callback with information about when
+ * the node should be considerated intercected or not.
+ * @param {Object} node - The DOM node to get the parentNode to be used as a root
+ * @param {Number|String|Array} threshold - Intersection point that will fire the callback
+ * @returns {Function} Callback with options to be fired when observable passes the threshold.
+ */
+ module.exports.setObserver = (node, threshold, rootMargin = null, cb) => {
+ const options = {
+ // We always rely on element's parentNode. original API defaults to window
+ root: node.parentNode,
+ // Threshold at 0 means element is fully hidden and 1 means fully visible
+ threshold: threshold,
+ // rootMargin is an optional gutter to include in the root element
+ // such as padding or margin. Accepts default CSS convention TOP RIGHT DOWN LEFT.
+ // As of Chrome 60, needs units such as pixel or percentage to work
+ rootMargin: rootMargin
+ }
+ return new window.IntersectionObserver(cb, options)
+ }
+
+ /**
+ * Checks whether or not the entry in question is being intersected by the parent
+ * @param {Object} state - The application's current window state
+ * @param {string} component - The component to watch intersection
+ * @param {Number|null} ratio - the intersection ratio to listen for
+ */
+ module.exports.isEntryIntersected = (state, component, ratio = null) => {
+ // intersectionRatio === 1 means the element is not being intercected
+ // so if ratio is undefined check for a minimum intersection
+ if (ratio == null) {
+ return state.getIn(['ui', component, 'intersectionRatio']) < noIntersection
+ }
+ return state.getIn(['ui', component, 'intersectionRatio']) <= ratio
+ }
diff --git a/app/renderer/lib/tabUtil.js b/app/renderer/lib/tabUtil.js
index dcda77921cb..3ee963e5fc4 100644
--- a/app/renderer/lib/tabUtil.js
+++ b/app/renderer/lib/tabUtil.js
@@ -2,41 +2,6 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
-const styles = require('../components/styles/global')
-
-/**
- * Get tab's breakpoint name for current tab size.
- * @param {Number} tabWidth current tab size
- * @returns {String} The matching breakpoint.
- */
-module.exports.getTabBreakpoint = (tabWidth) => {
- const sizes = ['default', 'large', 'largeMedium', 'medium', 'mediumSmall', 'small', 'extraSmall', 'smallest']
- let currentSize
-
- sizes.map(size => {
- if (tabWidth <= Number.parseInt(styles.breakpoint.tab[size], 10)) {
- currentSize = size
- return false
- }
- return true
- })
- return currentSize
-}
-
-// Execute resize handler at a rate of 15fps
-module.exports.tabUpdateFrameRate = 66
-
-/**
- * Check whether or not current breakpoint match defined criteria
- * @param {Object} breakpoint - Break point value
- * @param {Array} arr - Array of Strings including breakpoint names to check against
- * @returns {Boolean} Whether or not the sizing criteria was match
- */
-module.exports.hasBreakpoint = (breakpoint, arr) => {
- arr = Array.isArray(arr) ? arr : [arr]
- return arr.includes(breakpoint)
-}
-
/**
* Check whether or not the related target is a tab
* by checking the parentNode dataset
diff --git a/app/renderer/reducers/tabContentReducer.js b/app/renderer/reducers/tabContentReducer.js
new file mode 100644
index 00000000000..58de6b18c09
--- /dev/null
+++ b/app/renderer/reducers/tabContentReducer.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+ 'use strict'
+
+ const frameStateUtil = require('../../../js/state/frameStateUtil')
+ const {makeImmutable} = require('../../common/state/immutableUtil')
+ const windowConstants = require('../../../js/constants/windowConstants')
+
+ const tabContentReducer = (state, action, immutableAction) => {
+ action = immutableAction || makeImmutable(action)
+ switch (action.get('actionType')) {
+ case windowConstants.WINDOW_SET_TAB_CONTENT_INTERSECTION_STATE:
+ const firstTabOfTabSet = frameStateUtil
+ .isFirstFrameKeyInTabPage(state, action.get('frameKey'))
+
+ // since all unpinned tabs in a tabPage share the same size,
+ // only computes the intersection state for the first tab in the current tab set.
+ // this gives us a huge performance boost.
+ if (firstTabOfTabSet) {
+ state = state.setIn(['ui', 'tabs', 'intersectionRatio'], action.get('ratio'))
+ break
+ }
+ break
+ }
+ return state
+ }
+
+ module.exports = tabContentReducer
diff --git a/docs/state.md b/docs/state.md
index 42fc712da63..2b52319ffac 100644
--- a/docs/state.md
+++ b/docs/state.md
@@ -438,7 +438,6 @@ WindowStore
audioMuted: boolean, // frame is muted
audioPlaybackActive: boolean, // frame is playing audio
basicAuthDetail: object,
- breakpoint: string, // breakpoint name for current tab size, specified in app/renderer/components/styles/tab.js
closedAtIndex: number, // index the frame was last closed at, cleared unless the frame is inside of closedFrames
computedThemeColor: string, // CSS computed theme color from the favicon
endtLoadTime: datetime,
@@ -660,6 +659,7 @@ WindowStore
},
tabs: {
hoverTabIndex: number, // index of the current hovered tab
+ intersectionRatio: number, // at which tab size position the tab sentinel is being intersected
previewMode: boolean, // whether or not tab preview should be fired based on mouse idle time
previewTabPageIndex: number, // index of the tab being previewed
tabPageIndex: number // index of the current tab page
diff --git a/js/actions/windowActions.js b/js/actions/windowActions.js
index 0ea934a094b..fdd3b2f32b0 100644
--- a/js/actions/windowActions.js
+++ b/js/actions/windowActions.js
@@ -250,17 +250,11 @@ const windowActions = {
})
},
- /**
- * Dispatches a message to the store to set the tab breakpoint.
- *
- * @param {Object} frameKey - the frame key for the webview in question.
- * @param {string} breakpoint - the tab breakpoint to change to
- */
- setTabBreakpoint: function (frameKey, breakpoint) {
+ setTabIntersectionState: function (frameKey, ratio) {
dispatch({
- actionType: windowConstants.WINDOW_SET_TAB_BREAKPOINT,
+ actionType: windowConstants.WINDOW_SET_TAB_CONTENT_INTERSECTION_STATE,
frameKey,
- breakpoint
+ ratio
})
},
diff --git a/js/constants/windowConstants.js b/js/constants/windowConstants.js
index e63cc3ea8e9..7412c10c949 100644
--- a/js/constants/windowConstants.js
+++ b/js/constants/windowConstants.js
@@ -12,9 +12,9 @@ const windowConstants = {
WINDOW_SET_FOCUSED_FRAME: _,
WINDOW_SET_PREVIEW_TAB_PAGE_INDEX: _,
WINDOW_SET_TAB_PAGE_INDEX: _,
- WINDOW_SET_TAB_BREAKPOINT: _,
WINDOW_SET_TAB_HOVER_STATE: _,
WINDOW_SET_TAB_PAGE_HOVER_STATE: _,
+ WINDOW_SET_TAB_CONTENT_INTERSECTION_STATE: _,
WINDOW_TAB_MOVE: _,
WINDOW_SET_THEME_COLOR: _,
WINDOW_WEBVIEW_LOAD_END: _,
diff --git a/js/state/frameStateUtil.js b/js/state/frameStateUtil.js
index 4fc1f8d702a..91ac0560d62 100644
--- a/js/state/frameStateUtil.js
+++ b/js/state/frameStateUtil.js
@@ -1,6 +1,6 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
+* License, v. 2.0. If a copy of the MPL was not distributed with this file,
+* You can obtain one at http://mozilla.org/MPL/2.0/. */
const Immutable = require('immutable')
@@ -14,6 +14,7 @@ const webviewActions = require('../actions/webviewActions')
// State
const {makeImmutable} = require('../../app/common/state/immutableUtil')
+const tabState = require('../../app/common/state/tabState')
// Utils
const {getSetting} = require('../settings')
@@ -120,9 +121,9 @@ function getTabIdsByNonPinnedDisplayIndex (state) {
.map((frame) => frame.get('tabId'))
}
- /**
- * Obtains the display index for the specified tab ID excluding pins
- */
+/**
+* Obtains the display index for the specified tab ID excluding pins
+*/
function findNonPinnedDisplayIndexForTabId (state, tabId) {
return getTabIdsByNonPinnedDisplayIndex(state)
.findIndex((displayKey) => displayKey === tabId)
@@ -194,6 +195,11 @@ function getIndexByTabId (state, tabId) {
return index == null ? -1 : index
}
+const getTabIdByFrameKey = (state, frameKey) => {
+ const frame = getFrameByKey(state, frameKey)
+ return frame && frame.get('tabId', tabState.TAB_ID_NONE)
+}
+
function getActiveFrame (state) {
const activeFrameIndex = getActiveFrameIndex(state)
return state.get('frames').get(activeFrameIndex)
@@ -245,24 +251,24 @@ function getPreviousFrame (state) {
}
/**
- * Obtains the display index for the specified frame key
- */
+* Obtains the display index for the specified frame key
+*/
function findDisplayIndexForFrameKey (state, key) {
return getFrameKeysByDisplayIndex(state).findIndex((displayKey) => displayKey === key)
}
/**
- * Determines if the specified frame was opened from the specified
- * ancestorFrameKey.
- *
- * For example you may go to google.com and open 3 links in new tabs:
- * G g1 g2 g3
- * Then you may change to g1 and open another tab:
- * G g1 g1.1 g2 g3
- * But then you may go back to google.com and open another tab.
- * It should go like so:
- * G g1 g1.1 g2 g3 g4
- */
+* Determines if the specified frame was opened from the specified
+* ancestorFrameKey.
+*
+* For example you may go to google.com and open 3 links in new tabs:
+* G g1 g2 g3
+* Then you may change to g1 and open another tab:
+* G g1 g1.1 g2 g3
+* But then you may go back to google.com and open another tab.
+* It should go like so:
+* G g1 g1.1 g2 g3 g4
+*/
function isAncestorFrameKey (state, frame, parentFrameKey) {
if (!frame || !frame.get('parentFrameKey')) {
return false
@@ -321,9 +327,9 @@ const frameOptsFromFrame = (frame) => {
}
/**
- * Adds a frame specified by frameOpts and newKey and sets the activeFrameKey
- * @return Immutable top level application state ready to merge back in
- */
+* Adds a frame specified by frameOpts and newKey and sets the activeFrameKey
+* @return Immutable top level application state ready to merge back in
+*/
function addFrame (state, frameOpts, newKey, partitionNumber, openInForeground, insertionIndex) {
const frames = state.get('frames')
@@ -393,9 +399,9 @@ function addFrame (state, frameOpts, newKey, partitionNumber, openInForeground,
}
/**
- * Removes a frame specified by frameProps
- * @return Immutable top level application state ready to merge back in
- */
+* Removes a frame specified by frameProps
+* @return Immutable top level application state ready to merge back in
+*/
function removeFrame (state, frameProps, framePropsIndex) {
const frames = state.get('frames')
let closedFrames = state.get('closedFrames') || Immutable.List()
@@ -458,20 +464,38 @@ function getTotalBlocks (frame) {
}
/**
- * Check if frame is pinned or not
- */
+* Check if frame is pinned or not
+*/
function isPinned (state, frameKey) {
const frame = getFrameByKey(state, frameKey)
return frame && !!frame.get('pinnedLocation')
}
+const isFirstFrameKeyInTabPage = (state, frameKey) => {
+ const pageIndex = getTabPageIndex(state)
+ const tabsPerTabPage = Number(getSetting(settings.TABS_PER_PAGE))
+ const startingFrameIndex = pageIndex * tabsPerTabPage
+ const unpinnedTabs = getNonPinnedFrames(state) || Immutable.List()
+ const firstFrame = unpinnedTabs
+ .slice(startingFrameIndex, startingFrameIndex + tabsPerTabPage).first()
+
+ return firstFrame.get('key') === frameKey
+}
+
+const getTabPageIndex = (state) => {
+ const tabPageIndex = state.getIn(['ui', 'tabs', 'tabPageIndex'], 0)
+ const previewTabPageIndex = state.getIn(['ui', 'tabs', 'previewTabPageIndex'])
+
+ return previewTabPageIndex || tabPageIndex
+}
+
/**
- * Updates the tab page index to the specified frameProps
- * @param state{Object} - Window state
- * @param tabId{number} - Tab id for the frame
- * @param tabsPerPage{string} - Current setting for tabs per page, with a default value
- */
+* Updates the tab page index to the specified frameProps
+* @param state{Object} - Window state
+* @param tabId{number} - Tab id for the frame
+* @param tabsPerPage{string} - Current setting for tabs per page, with a default value
+*/
function updateTabPageIndex (state, tabId, tabsPerPage = getSetting(settings.TABS_PER_PAGE)) {
const index = getFrameTabPageIndex(state, tabId, tabsPerPage)
@@ -594,23 +618,23 @@ const setPreviewTabPageIndex = (state, index, immediate = false) => {
}
/**
- * Defines whether or not a tab should be allowed to preview its content
- * based on mouse idle time defined by mouse move in tab.js
- * @see windowConstants.WINDOW_TAB_MOUSE_MOVE for information
- * on how the data is handled in the store.
- * @param state {Object} - Application state
- * @param previewMode {Boolean} - Whether or not minimium idle time
- * has match the criteria
- */
+* Defines whether or not a tab should be allowed to preview its content
+* based on mouse idle time defined by mouse move in tab.js
+* @see windowConstants.WINDOW_TAB_MOUSE_MOVE for information
+* on how the data is handled in the store.
+* @param state {Object} - Application state
+* @param previewMode {Boolean} - Whether or not minimium idle time
+* has match the criteria
+*/
const setPreviewMode = (state, previewMode) => {
return state.setIn(['ui', 'tabs', 'previewMode'], previewMode)
}
/**
- * Gets the previewMode application state
- * @param state {Object} - Application state
- * @return Immutable top level application state for previewMode
- */
+* Gets the previewMode application state
+* @param state {Object} - Application state
+* @return Immutable top level application state for previewMode
+*/
const getPreviewMode = (state) => {
return state.getIn(['ui', 'tabs', 'previewMode'])
}
@@ -652,36 +676,36 @@ const setTabPageHoverState = (state, tabPageIndex, hoverState) => {
}
/**
- * Checks if the current tab index is being hovered
- * @param state {Object} - Application state
- * @param frameKey {Number} - The current tab's frameKey
- * @return Boolean - wheter or not hoverState is true
- */
+* Checks if the current tab index is being hovered
+* @param state {Object} - Application state
+* @param frameKey {Number} - The current tab's frameKey
+* @return Boolean - wheter or not hoverState is true
+*/
const getTabHoverState = (state, frameKey) => {
const index = getFrameIndex(state, frameKey)
return getHoverTabIndex(state) === index
}
/**
- * Gets the hovered tab index state
- * This check will return null if no tab is being hovered
- * and is used getTabHoverState to check if current index is being hovered.
- * If the method to apply for does not know the right index
- * this should be used instead of getTabHoverState
- * @param state {Object} - Application state
- * @return Immutable top level application state for hoverTabIndex
- */
+* Gets the hovered tab index state
+* This check will return null if no tab is being hovered
+* and is used getTabHoverState to check if current index is being hovered.
+* If the method to apply for does not know the right index
+* this should be used instead of getTabHoverState
+* @param state {Object} - Application state
+* @return Immutable top level application state for hoverTabIndex
+*/
const getHoverTabIndex = (state) => {
return state.getIn(['ui', 'tabs', 'hoverTabIndex'])
}
/**
- * Sets the hover state for current tab index in top level state
- * @param state {Object} - Application state
- * @param frameKey {Number} - The current tab's frameKey
- * @param hoverState {Boolean} - True if the current tab is being hovered.
- * @return Immutable top level application state for hoverTabIndex
- */
+* Sets the hover state for current tab index in top level state
+* @param state {Object} - Application state
+* @param frameKey {Number} - The current tab's frameKey
+* @param hoverState {Boolean} - True if the current tab is being hovered.
+* @return Immutable top level application state for hoverTabIndex
+*/
const setHoverTabIndex = (state, frameKey, hoverState) => {
const frameIndex = getFrameIndex(state, frameKey)
if (!hoverState) {
@@ -692,13 +716,13 @@ const setHoverTabIndex = (state, frameKey, hoverState) => {
}
/**
- * Gets values from the window setTabHoverState action from the store
- * and is used to apply both hoverState and previewFrameKey
- * @param state {Object} - Application state
- * @param frameKey {Number} - The current tab's frameKey
- * @param hoverState {Boolean} - True if the current tab is being hovered.
- * @return Immutable top level application state for hoverTabIndex
- */
+* Gets values from the window setTabHoverState action from the store
+* and is used to apply both hoverState and previewFrameKey
+* @param state {Object} - Application state
+* @param frameKey {Number} - The current tab's frameKey
+* @param hoverState {Boolean} - True if the current tab is being hovered.
+* @return Immutable top level application state for hoverTabIndex
+*/
const setTabHoverState = (state, frameKey, hoverState, enablePreviewMode) => {
const frameIndex = getFrameIndex(state, frameKey)
if (frameIndex !== -1) {
@@ -709,7 +733,21 @@ const setTabHoverState = (state, frameKey, hoverState, enablePreviewMode) => {
return state
}
+const frameLocationMatch = (frame, location) => {
+ if (frame == null) {
+ return false
+ }
+ const validFrame = Immutable.Map.isMap(frame)
+ return validFrame && frame.get('location') === location
+}
+
+const hasFrame = (state, frameKey) => {
+ const frame = getFrameByKey(state, frameKey)
+ return frame && !frame.isEmpty()
+}
+
module.exports = {
+ hasFrame,
setTabPageHoverState,
setPreviewTabPageIndex,
getTabHoverState,
@@ -745,6 +783,7 @@ module.exports = {
getFrameByKey,
getFrameByTabId,
getIndexByTabId,
+ getTabIdByFrameKey,
getPartitionNumber,
setFrameLastAccessedTime,
setActiveFrameKey,
@@ -766,9 +805,12 @@ module.exports = {
onFindBarHide,
getTotalBlocks,
isPinned,
+ isFirstFrameKeyInTabPage,
+ getTabPageIndex,
updateTabPageIndex,
isValidClosedFrame,
getTabPageCount,
getSortedFrameKeys,
- frameStatePathByTabId
+ frameStatePathByTabId,
+ frameLocationMatch
}
diff --git a/js/stores/windowStore.js b/js/stores/windowStore.js
index c7d99c40c0d..2d2bea8055b 100644
--- a/js/stores/windowStore.js
+++ b/js/stores/windowStore.js
@@ -220,6 +220,7 @@ const applyReducers = (state, action, immutableAction) => [
require('../../app/renderer/reducers/urlBarReducer'),
require('../../app/renderer/reducers/frameReducer'),
require('../../app/renderer/reducers/contextMenuReducer'),
+ require('../../app/renderer/reducers/tabContentReducer'),
// This should be included even in production builds since you can use
// an environment variable to show the Debug menu
require('../../app/renderer/reducers/debugReducer')
@@ -358,17 +359,6 @@ const doAction = (action) => {
windowState = frameStateUtil.updateTabPageIndex(windowState, action.frameProps.get('tabId'))
}
break
- case windowConstants.WINDOW_SET_TAB_BREAKPOINT:
- {
- if (!action.frameKey) {
- break
- }
- const frameIndex = frameStateUtil.getFrameIndex(windowState, action.frameKey)
- if (frameIndex !== -1) {
- windowState = windowState.setIn(['frames', frameIndex, 'breakpoint'], action.breakpoint)
- }
- break
- }
case windowConstants.WINDOW_TAB_MOUSE_MOVE:
{
// previewMode is only triggered if mouse is idle over a tab
diff --git a/less/main.less b/less/main.less
index 819d480a1f1..5b64920513f 100644
--- a/less/main.less
+++ b/less/main.less
@@ -3,7 +3,7 @@
* You can obtain one at http://mozilla.org/MPL/2.0/. */
@bodyBG: #000;
-@windowContainerFG: #f00;
+@windowContainerFG: #000;
body {
background-color: @bodyBG;
diff --git a/less/tabs.less b/less/tabs.less
index 9ebbdf0e37f..ea20d58b23d 100644
--- a/less/tabs.less
+++ b/less/tabs.less
@@ -10,7 +10,7 @@
flex: 1;
overflow: auto;
padding: 0;
- height: @tabsToolbarHeight;
+ height: -webkit-fill-available;
position: relative;
white-space: nowrap;
z-index: @zindexTabs;
@@ -59,6 +59,7 @@
position: relative;
vertical-align: top;
overflow: hidden;
+ height: -webkit-fill-available;
// There's a special case that tabs should span the full width
// if there are a full set of them.
&:not(.partOfFullPageSet) {
@@ -122,14 +123,17 @@
box-sizing: border-box;
background: @tabsBackground;
display: flex;
- height: @tabsToolbarHeight;
+ // This element is set as border-box so it does not
+ // take into account the borders as width gutter, so we
+ // increase its size by 1px to include the top border
+ height: calc(~'@{tabsToolbarHeight} + 1px');
border-top: 1px solid @tabsToolbarBorderColor;
user-select: none;
}
.tabsToolbarButtons {
box-sizing: border-box;
- height: @tabHeight;
+ height: -webkit-fill-available;
padding-right: 5px;
.browserButton {
@@ -226,6 +230,8 @@
}
.pinnedTabs {
+ height: -webkit-fill-available;
+ box-sizing: border-box;
margin-left: 0;
margin-top: 0;
background: @tabsBackgroundInactive;
diff --git a/less/window.less b/less/window.less
index a55ee98fa55..e161d1d9864 100644
--- a/less/window.less
+++ b/less/window.less
@@ -164,3 +164,12 @@ body.fullScreen .banner.fade {
a {
cursor: pointer;
}
+
+// TODO: Investigate if these are required
+// See PR #10861, Issue #10877
+body {
+ background-color: #000;
+}
+#windowContainer {
+ color: #000;
+}
diff --git a/test/unit/app/browser/reducers/tabContentReducerTest.js b/test/unit/app/browser/reducers/tabContentReducerTest.js
new file mode 100644
index 00000000000..e2b6fb46340
--- /dev/null
+++ b/test/unit/app/browser/reducers/tabContentReducerTest.js
@@ -0,0 +1,63 @@
+/* global describe, it, before, after */
+const mockery = require('mockery')
+const Immutable = require('immutable')
+const assert = require('assert')
+const fakeElectron = require('../../../lib/fakeElectron')
+
+const windowConstants = require('../../../../../js/constants/windowConstants')
+require('../../../braveUnit')
+
+describe('tabContentReducer', function () {
+ let tabContentReducer
+ before(function () {
+ mockery.enable({
+ warnOnReplace: false,
+ warnOnUnregistered: false,
+ useCleanCache: true
+ })
+ mockery.registerMock('electron', fakeElectron)
+ tabContentReducer = require('../../../../../app/renderer/reducers/tabContentReducer')
+ })
+
+ after(function () {
+ mockery.disable()
+ })
+ describe('WINDOW_SET_TAB_CONTENT_INTERSECTION_STATE', function () {
+ const frameKey = 1
+ const originalRatio = 0.7
+ const newRatio = 1337
+
+ before(function () {
+ this.action = Immutable.fromJS({
+ actionType: windowConstants.WINDOW_SET_TAB_CONTENT_INTERSECTION_STATE,
+ frameKey: frameKey,
+ ratio: originalRatio
+ })
+ this.state = Immutable.fromJS({
+ frames: [{
+ key: frameKey,
+ location: 'http://clifton-loves-the-kaisen-philosophy.com'
+ }],
+ ui: { tabs: { intersectionRatio: originalRatio } }
+ })
+ })
+
+ it('sets the intersection ratio if current tab is the first in the first tabPage', function () {
+ this.action = this.action.set('ratio', newRatio)
+ this.newState = tabContentReducer(this.state, this.action)
+ const result = this.newState.getIn(['ui', 'tabs', 'intersectionRatio'])
+ assert.equal(result, newRatio)
+ })
+
+ it('does not set the intersection if current tab is not the first in a tab page', function () {
+ this.action = this.action.set('ratio', newRatio)
+ this.state = this.state.mergeIn(['frames', 0], {
+ key: 2,
+ location: 'something'
+ })
+ this.newState = tabContentReducer(this.state, this.action)
+ const result = this.newState.getIn(['ui', 'tabs', 'intersectionRatio'])
+ assert.equal(result, originalRatio)
+ })
+ })
+})
diff --git a/test/unit/app/common/state/tabContentStateTest.js b/test/unit/app/common/state/tabContentStateTest.js
deleted file mode 100644
index 9b9bbec5928..00000000000
--- a/test/unit/app/common/state/tabContentStateTest.js
+++ /dev/null
@@ -1,545 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/* global describe, it, before, beforeEach, after, afterEach */
-
-const assert = require('assert')
-const Immutable = require('immutable')
-const mockery = require('mockery')
-const sinon = require('sinon')
-const fakeElectron = require('../../../lib/fakeElectron')
-const {braveExtensionId} = require('../../../../../js/constants/config')
-const styles = require('../../../../../app/renderer/components/styles/global')
-
-const frameKey = 1
-const index = 0
-const defaultWindowStore = Immutable.fromJS({
- activeFrameKey: frameKey,
- frames: [{
- key: frameKey,
- tabId: 1,
- location: 'http://brave.com'
- }],
- tabs: [{
- key: frameKey,
- index: index
- }],
- framesInternal: {
- index: {
- 1: 0
- },
- tabIndex: {
- 1: 0
- }
- },
- ui: {
- tabs: {
- hoverTabIndex: index
- }
- }
-})
-
-describe('tabContentState unit tests', function () {
- let tabContentState
- let frameStateUtil
- let getFrameByKeyMock
- let defaultValue
- let isFrameKeyActive
-
- before(function () {
- mockery.enable({
- warnOnReplace: false,
- warnOnUnregistered: false,
- useCleanCache: true
- })
- frameStateUtil = require('../../../../../js/state/frameStateUtil')
- mockery.registerMock('electron', fakeElectron)
- mockery.registerMock('../../../js/l10n', {
- translation: () => 'translated'
- })
- mockery.registerMock('../../../js/state/frameStateUtil', frameStateUtil)
- mockery.registerMock('../../../js/settings', {
- getSetting: () => defaultValue
- })
- tabContentState = require('../../../../../app/common/state/tabContentState')
- })
-
- beforeEach(function () {
- defaultValue = true
- })
-
- after(function () {
- mockery.deregisterAll()
- mockery.disable()
- })
-
- afterEach(function () {
- if (getFrameByKeyMock) {
- getFrameByKeyMock.restore()
- getFrameByKeyMock = undefined
- }
-
- if (isFrameKeyActive) {
- isFrameKeyActive.restore()
- isFrameKeyActive = undefined
- }
- })
-
- describe('getDisplayTitle', function () {
- it('should return empty string if frame is not found', function * () {
- const result = tabContentState.getDisplayTitle(defaultWindowStore, 0)
- assert.equal(result, '')
- })
-
- it('should return translated title for about:blank', function * () {
- const windowStore = defaultWindowStore.mergeIn(['frames', 0], {
- location: 'about:blank'
- })
- const result = tabContentState.getDisplayTitle(windowStore, frameKey)
- assert.equal(result, 'translated')
- })
-
- it('should return translated title for about:newtab', function * () {
- const windowStore = defaultWindowStore.mergeIn(['frames', 0], {
- location: 'about:blank'
- })
- const result = tabContentState.getDisplayTitle(windowStore, frameKey)
- assert.equal(result, 'translated')
- })
-
- it('should return title', function * () {
- const title = 'Brave'
- const windowStore = defaultWindowStore.mergeIn(['frames', 0], {
- title: title
- })
- const result = tabContentState.getDisplayTitle(windowStore, frameKey)
- assert.equal(result, title)
- })
-
- it('should return location if title is not provided', function * () {
- const result = tabContentState.getDisplayTitle(defaultWindowStore, frameKey)
- assert.equal(result, defaultWindowStore.getIn(['frames', 0, 'location']))
- })
-
- it('should replace play indicator from the title (added by Youtube)', function * () {
- const windowStore = defaultWindowStore.mergeIn(['frames', 0], {
- title: '▶ Brave'
- })
- const result = tabContentState.getDisplayTitle(windowStore, frameKey)
- assert.equal(result, 'Brave')
- })
- })
-
- describe('isTabLoading', function () {
- it('handles frame being null/undefined', function () {
- assert.equal(tabContentState.isTabLoading(), false)
- })
-
- describe('when provisionalLocation is not set', function () {
- it('returns true if frame.loading', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', (state, frameKey) => {
- return Immutable.fromJS({loading: true})
- })
- assert.equal(tabContentState.isTabLoading(), true)
- })
- it('returns true if location is about:blank', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', (state, frameKey) => {
- return Immutable.fromJS({location: 'about:blank'})
- })
- assert.equal(tabContentState.isTabLoading(), true)
- })
- })
-
- describe('when provisionalLocation is set', function () {
- it('returns false if loading and provisionalLocation is a brave about page', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', (state, frameKey) => {
- return Immutable.fromJS({
- loading: true,
- provisionalLocation: `chrome-extension://${braveExtensionId}/pageGoesHere`
- })
- })
- assert.equal(tabContentState.isTabLoading(), false)
- })
- it('returns true if loading and provisionalLocation is not a brave about page', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', (state, frameKey) => {
- return Immutable.fromJS({
- loading: true,
- provisionalLocation: 'https://brave.com'
- })
- })
- assert.equal(tabContentState.isTabLoading(), true)
- })
- })
- })
-
- describe('isMediumView', function () {
- it('handles frame being null/undefined', function () {
- assert.equal(tabContentState.isMediumView(), false)
- })
-
- it('returns true if valid', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', (state, frameKey) => {
- return Immutable.fromJS({breakpoint: 'large'})
- })
- assert.equal(tabContentState.isMediumView(), true)
- })
- })
-
- describe('isNarrowView', function () {
- it('returns false if null/undefined', function () {
- assert.equal(tabContentState.isNarrowView(), false)
- })
-
- it('returns true if valid', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', (state, frameKey) => {
- return Immutable.fromJS({breakpoint: 'small'})
- })
- assert.equal(tabContentState.isNarrowView(), true)
- })
- })
-
- describe('isNarrowestView', function () {
- it('handles frame being null/undefined', function () {
- assert.equal(tabContentState.isNarrowestView(), false)
- })
-
- it('returns true if valid', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', (state, frameKey) => {
- return Immutable.fromJS({breakpoint: 'extraSmall'})
- })
- assert.equal(tabContentState.isNarrowestView(), true)
- })
- })
-
- describe('getThemeColor', function () {
- it('handles frame being null/undefined', function () {
- assert.equal(tabContentState.getThemeColor(), false)
- })
-
- it('if PAINT_TABS is false', function () {
- defaultValue = false
- assert.equal(tabContentState.getThemeColor(), false)
- })
-
- it('if PAINT_TABS is true, but dont have themeColor', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return null
- })
- assert.equal(tabContentState.getThemeColor(), false)
- })
-
- it('if PAINT_TABS is true and have themeColor', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- themeColor: '#F00'
- })
- })
- assert.equal(tabContentState.getThemeColor(), '#F00')
- })
-
- it('if PAINT_TABS is true and have computedThemeColor', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- computedThemeColor: '#FFF'
- })
- })
- assert.equal(tabContentState.getThemeColor(), '#FFF')
- })
-
- it('if PAINT_TABS is true and both theme colors are provided', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- themeColor: '#F00',
- computedThemeColor: '#FFF'
- })
- })
- assert.equal(tabContentState.getThemeColor(), '#F00')
- })
- })
-
- describe('canPlayAudio', function () {
- it('handles frame being null/undefined', function () {
- assert.equal(tabContentState.canPlayAudio(), false)
- })
-
- it('if audioPlaybackActive and audioMuted is not defined', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return null
- })
- assert.equal(tabContentState.canPlayAudio(), false)
- })
-
- it('if audioPlaybackActive is true', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- audioPlaybackActive: true
- })
- })
- assert.equal(tabContentState.canPlayAudio(), true)
- })
-
- it('if audioMuted is true', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- audioMuted: true
- })
- })
- assert.equal(tabContentState.canPlayAudio(), true)
- })
-
- it('if both provided', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- audioPlaybackActive: true,
- audioMuted: false
- })
- })
- assert.equal(tabContentState.canPlayAudio(), true)
- })
- })
-
- describe('getPageIndex', function () {
- it('handles frame being null/undefined', function () {
- const state = Immutable.fromJS({})
- assert.equal(tabContentState.getPageIndex(state), 0)
- })
-
- it('tabPageIndex is provided', function () {
- const state = Immutable.fromJS({
- ui: {
- tabs: {
- tabPageIndex: 1
- }
- }
- })
- assert.equal(tabContentState.getPageIndex(state), 1)
- })
-
- it('previewTabPageIndex is provided', function () {
- const state = Immutable.fromJS({
- ui: {
- tabs: {
- previewTabPageIndex: 1
- }
- }
- })
- assert.equal(tabContentState.getPageIndex(state), 1)
- })
-
- it('both are provided', function () {
- const state = Immutable.fromJS({
- ui: {
- tabs: {
- previewTabPageIndex: 1,
- tabPageIndex: 2
- }
- }
- })
- assert.equal(tabContentState.getPageIndex(state), 1)
- })
- })
-
- describe('getTabIconColor', function () {
- it('handles frame being null/undefined', function () {
- const state = Immutable.fromJS({})
- assert.equal(tabContentState.getTabIconColor(state), '')
- })
-
- it('tab is private and active', function () {
- const state = defaultWindowStore
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- isPrivate: true
- })
- })
- isFrameKeyActive = sinon.stub(frameStateUtil, 'isFrameKeyActive', () => {
- return true
- })
- assert.equal(tabContentState.getTabIconColor(state), styles.color.white100)
- })
-
- it('tab is not private and active, but paint is disabled', function () {
- const state = defaultWindowStore
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- isPrivate: false
- })
- })
- isFrameKeyActive = sinon.stub(frameStateUtil, 'isFrameKeyActive', () => {
- return true
- })
- defaultValue = false
- assert.equal(tabContentState.getTabIconColor(state), styles.color.black100)
- })
-
- it('all valid', function () {
- const state = defaultWindowStore
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- isPrivate: false,
- themeColor: '#F00'
- })
- })
- isFrameKeyActive = sinon.stub(frameStateUtil, 'isFrameKeyActive', () => {
- return true
- })
- assert.equal(tabContentState.getTabIconColor(state), 'white')
- })
- })
-
- describe('hasFixedCloseIcon', function () {
- it('handles frame being null/undefined', function () {
- const state = Immutable.fromJS({})
- assert.equal(tabContentState.hasFixedCloseIcon(state), false)
- })
-
- it('frame is not active', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- breakpoint: 'default'
- })
- })
- isFrameKeyActive = sinon.stub(frameStateUtil, 'isFrameKeyActive', () => {
- return false
- })
- assert.equal(tabContentState.hasFixedCloseIcon(), false)
- })
-
- it('frame is active and breakpoint is small', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- breakpoint: 'small'
- })
- })
- isFrameKeyActive = sinon.stub(frameStateUtil, 'isFrameKeyActive', () => {
- return true
- })
- assert.equal(tabContentState.hasFixedCloseIcon(), true)
- })
-
- it('frame is active and breakpoint is default', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- breakpoint: 'default'
- })
- })
- isFrameKeyActive = sinon.stub(frameStateUtil, 'isFrameKeyActive', () => {
- return true
- })
- assert.equal(tabContentState.hasFixedCloseIcon(), false)
- })
- })
-
- describe('hasRelativeCloseIcon', function () {
- it('handles frame being null/undefined', function () {
- const state = Immutable.fromJS({})
- assert.equal(tabContentState.hasRelativeCloseIcon(state), false)
- })
-
- it('if not hovering (tabIndex !== hoverTabIndex)', function () {
- const state = defaultWindowStore.setIn(['ui', 'tabs', 'hoverTabIndex'], null)
- assert.equal(tabContentState.hasRelativeCloseIcon(state, frameKey), false)
- })
-
- it('if hovering (tabIndex === hoverTabIndex) and break point is small', function () {
- const state = defaultWindowStore
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- breakpoint: 'small'
- })
- })
- assert.equal(tabContentState.hasRelativeCloseIcon(state, frameKey), false)
- })
-
- it('if hovering (tabIndex === hoverTabIndex) and break point is default', function () {
- const state = defaultWindowStore
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- breakpoint: 'default'
- })
- })
- assert.equal(tabContentState.hasRelativeCloseIcon(state, frameKey), true)
- })
- })
-
- describe('hasVisibleSecondaryIcon', function () {
- let hasRelativeCloseIcon, hasFixedCloseIcon
-
- afterEach(function () {
- if (hasRelativeCloseIcon) {
- hasRelativeCloseIcon.restore()
- hasRelativeCloseIcon = undefined
- }
- if (hasFixedCloseIcon) {
- hasFixedCloseIcon.restore()
- hasFixedCloseIcon = undefined
- }
- })
-
- it('handles frame being null/undefined', function () {
- const state = Immutable.fromJS({})
- assert.equal(tabContentState.hasVisibleSecondaryIcon(state), false)
- })
-
- it('hasRelativeCloseIcon, dont have hasFixedCloseIcon and is default', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- breakpoint: 'default'
- })
- })
- hasRelativeCloseIcon = sinon.stub(tabContentState, 'hasRelativeCloseIcon', () => {
- return true
- })
- hasFixedCloseIcon = sinon.stub(tabContentState, 'hasFixedCloseIcon', () => {
- return false
- })
- assert.equal(tabContentState.hasVisibleSecondaryIcon(), false)
- })
-
- it('dont have hasRelativeCloseIcon, have hasFixedCloseIcon and is default', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- breakpoint: 'default'
- })
- })
- hasRelativeCloseIcon = sinon.stub(tabContentState, 'hasRelativeCloseIcon', () => {
- return false
- })
- hasFixedCloseIcon = sinon.stub(tabContentState, 'hasFixedCloseIcon', () => {
- return true
- })
- assert.equal(tabContentState.hasVisibleSecondaryIcon(), false)
- })
-
- it('dont have hasRelativeCloseIcon, dont have hasFixedCloseIcon and is small', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- breakpoint: 'small'
- })
- })
- hasRelativeCloseIcon = sinon.stub(tabContentState, 'hasRelativeCloseIcon', () => {
- return false
- })
- hasFixedCloseIcon = sinon.stub(tabContentState, 'hasFixedCloseIcon', () => {
- return false
- })
- assert.equal(tabContentState.hasVisibleSecondaryIcon(), false)
- })
-
- it('dont have hasRelativeCloseIcon, dont have hasFixedCloseIcon and is default', function () {
- getFrameByKeyMock = sinon.stub(frameStateUtil, 'getFrameByKey', () => {
- return Immutable.fromJS({
- breakpoint: 'default'
- })
- })
- hasRelativeCloseIcon = sinon.stub(tabContentState, 'hasRelativeCloseIcon', () => {
- return false
- })
- hasFixedCloseIcon = sinon.stub(tabContentState, 'hasFixedCloseIcon', () => {
- return false
- })
- assert.equal(tabContentState.hasVisibleSecondaryIcon(), true)
- })
- })
-})
diff --git a/test/unit/app/common/state/tabContentStateTest/audioStateTest.js b/test/unit/app/common/state/tabContentStateTest/audioStateTest.js
new file mode 100644
index 00000000000..19aec879344
--- /dev/null
+++ b/test/unit/app/common/state/tabContentStateTest/audioStateTest.js
@@ -0,0 +1,164 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* global describe, it, before, after */
+
+const assert = require('assert')
+const Immutable = require('immutable')
+const mockery = require('mockery')
+const fakeElectron = require('../../../../lib/fakeElectron')
+const {intersection} = require('../../../../../../app/renderer/components/styles/global')
+
+const frameKey = 1
+const index = 0
+let defaultState = Immutable.fromJS({
+ activeFrameKey: frameKey,
+ frames: [{
+ key: frameKey,
+ tabId: 1,
+ location: 'http://brave.com'
+ }],
+ tabs: [{
+ key: frameKey,
+ index: index
+ }],
+ framesInternal: {
+ index: { 1: 0 },
+ tabIndex: { 1: 0 }
+ }
+})
+
+describe('audioState unit tests', function () {
+ let audioState
+
+ before(function () {
+ mockery.enable({
+ warnOnReplace: false,
+ warnOnUnregistered: false,
+ useCleanCache: true
+ })
+ mockery.registerMock('electron', fakeElectron)
+ audioState = require('../../../../../../app/common/state/tabContentState/audioState')
+ })
+
+ after(function () {
+ mockery.deregisterAll()
+ mockery.disable()
+ })
+
+ describe('canPlayAudio', function () {
+ it('returns false if frame is null/undefined', function * () {
+ assert.equal(audioState.canPlayAudio(), false)
+ })
+
+ it('returns true if audioPlaybackActive is true', function * () {
+ const state = defaultState.setIn(['frames', index, 'audioPlaybackActive'], true)
+ const result = audioState.canPlayAudio(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns true if audioMuted is true', function * () {
+ const state = defaultState.setIn(['frames', index, 'audioMuted'], true)
+ const result = audioState.canPlayAudio(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns true if both provided', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'audioMuted'], true)
+ .setIn(['frames', index, 'audioPlaybackActive'], true)
+ const result = audioState.canPlayAudio(state, frameKey)
+ assert.equal(result, true)
+ })
+ })
+
+ describe('isAudioMuted', function () {
+ it('returns false if frame is null/undefined', function * () {
+ assert.equal(audioState.isAudioMuted(), false)
+ })
+
+ it('returns true if audioMuted is true', function * () {
+ const state = defaultState.setIn(['frames', index, 'audioMuted'], true)
+ const result = audioState.isAudioMuted(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns false if audioMuted is false', function * () {
+ const state = defaultState.setIn(['frames', index, 'audioMuted'], false)
+ const result = audioState.isAudioMuted(state, frameKey)
+ assert.equal(result, false)
+ })
+ })
+
+ describe('showAudioIcon', function () {
+ it('returns false if frame is null/undefined', function * () {
+ assert.equal(audioState.showAudioIcon(), false)
+ })
+
+ it('returns true if tab can play audio and tab is not intercected', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'audioPlaybackActive'], true)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.noIntersection)
+ const result = audioState.showAudioIcon(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns false if tab can play audio and tab is intercected', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'audioPlaybackActive'], true)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at40)
+ const result = audioState.showAudioIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns false if tab can not play audio and tab is not intercected', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'audioPlaybackActive'], false)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at40)
+ const result = audioState.showAudioIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+ })
+
+ describe('showAudioTopBorder', function () {
+ it('returns false if frame is null/undefined', function * () {
+ assert.equal(audioState.showAudioTopBorder(), false)
+ })
+
+ it('returns true if tab can play audio and tab is not pinned but is intercected', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'audioPlaybackActive'], true)
+ .setIn(['frames', index, 'audioMuted'], false)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at40)
+ const result = audioState.showAudioTopBorder(state, frameKey, false)
+ assert.equal(result, true)
+ })
+
+ it('returns true if tab can play audio and is pinned', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'audioPlaybackActive'], true)
+ .setIn(['frames', index, 'audioMuted'], false)
+ const result = audioState.showAudioTopBorder(state, frameKey, true)
+ assert.equal(result, true)
+ })
+
+ it('returns false if tab can play audio and tab is not pinned and is not intercected', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'audioPlaybackActive'], true)
+ .setIn(['frames', index, 'audioMuted'], false)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.noIntersection)
+ const result = audioState.showAudioTopBorder(state, frameKey, false)
+ assert.equal(result, false)
+ })
+
+ it('returns false if tab can not play audio, not pinned and tab is intercected', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'audioPlaybackActive'], false)
+ .setIn(['frames', index, 'audioMuted'], false)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ const result = audioState.showAudioTopBorder(state, frameKey, false)
+ assert.equal(result, false)
+ })
+ })
+})
diff --git a/test/unit/app/common/state/tabContentStateTest/closeStateTest.js b/test/unit/app/common/state/tabContentStateTest/closeStateTest.js
new file mode 100644
index 00000000000..32f44f565c4
--- /dev/null
+++ b/test/unit/app/common/state/tabContentStateTest/closeStateTest.js
@@ -0,0 +1,165 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* global describe, it, before, after */
+
+const assert = require('assert')
+const Immutable = require('immutable')
+const mockery = require('mockery')
+const fakeElectron = require('../../../../lib/fakeElectron')
+const {intersection} = require('../../../../../../app/renderer/components/styles/global')
+
+const frameKey = 1
+const index = 0
+let defaultState = Immutable.fromJS({
+ activeFrameKey: frameKey,
+ frames: [{
+ key: frameKey,
+ tabId: 1,
+ location: 'http://brave.com'
+ }],
+ tabs: [{
+ key: frameKey,
+ index: index
+ }],
+ framesInternal: {
+ index: { 1: 0 },
+ tabIndex: { 1: 0 }
+ }
+})
+
+describe('closeState unit tests', function () {
+ let closeState
+
+ before(function () {
+ mockery.enable({
+ warnOnReplace: false,
+ warnOnUnregistered: false,
+ useCleanCache: true
+ })
+ mockery.registerMock('electron', fakeElectron)
+ closeState = require('../../../../../../app/common/state/tabContentState/closeState')
+ })
+
+ after(function () {
+ mockery.deregisterAll()
+ mockery.disable()
+ })
+
+ describe('hasFixedCloseIcon', function () {
+ it('returns false if frame is null/undefined', function * () {
+ assert.equal(closeState.hasFixedCloseIcon(), false)
+ })
+
+ it('returns true if tab is active and is intersected at 75% size', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at75)
+ const result = closeState.hasFixedCloseIcon(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns false if tab is not active and is intersected at 75% size', function * () {
+ const state = defaultState
+ .set('activeFrameKey', 1337)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at75)
+ const result = closeState.hasFixedCloseIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns false if tab is active and is not intersected', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.noIntersection)
+ const result = closeState.hasFixedCloseIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns true if tab is active and intersected below 75% size', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ const result = closeState.hasFixedCloseIcon(state, frameKey)
+ assert.equal(result, true)
+ })
+ })
+
+ describe('hasRelativeCloseIcon', function () {
+ it('returns false if frame is null/undefined', function * () {
+ assert.equal(closeState.hasRelativeCloseIcon(), false)
+ })
+
+ it('returns true if tab index is being hovered', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'hoverTabIndex'], index)
+ const result = closeState.hasRelativeCloseIcon(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns false if tab is not being hovered', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'hoverTabIndex'], 1337)
+ const result = closeState.hasRelativeCloseIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns false if tab is being intersected', function * () {
+ const state = defaultState
+ .mergeIn(['ui', 'tabs'], {
+ hoverTabIndex: index,
+ intersectionRatio: intersection.at75
+ })
+ const result = closeState.hasRelativeCloseIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+ })
+
+ describe('showCloseTabIcon', function () {
+ it('returns false if frame is null/undefined', function * () {
+ assert.equal(closeState.showCloseTabIcon(), false)
+ })
+
+ it('returns false if tab is being intersected at 15% size', function * () {
+ const state = defaultState
+ .mergeIn(['ui', 'tabs'], {
+ hoverTabIndex: index,
+ intersectionRatio: intersection.at20
+ })
+ const result = closeState.showCloseTabIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns false if tab not intersected and not hovered', function * () {
+ const state = defaultState
+ .mergeIn(['ui', 'tabs'], {
+ hoverTabIndex: 1337,
+ intersectionRatio: intersection.noIntersection
+ })
+ const result = closeState.showCloseTabIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns true if tab not intersected and hovered', function * () {
+ const state = defaultState
+ .mergeIn(['ui', 'tabs'], {
+ hoverTabIndex: index,
+ intersectionRatio: intersection.noIntersection
+ })
+ const result = closeState.showCloseTabIcon(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns false if tab is intersected and not active', function * () {
+ const state = defaultState
+ .set('activeFrameKey', 1337)
+ .setIn(['ui', 'tabs', 'intersectionRatio', intersection.at45])
+ const result = closeState.showCloseTabIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns true if tab is intersected and active', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at75)
+ const result = closeState.showCloseTabIcon(state, frameKey)
+ assert.equal(result, true)
+ })
+ })
+})
diff --git a/test/unit/app/common/state/tabContentStateTest/faviconStateTest.js b/test/unit/app/common/state/tabContentStateTest/faviconStateTest.js
new file mode 100644
index 00000000000..ea90d8ed18d
--- /dev/null
+++ b/test/unit/app/common/state/tabContentStateTest/faviconStateTest.js
@@ -0,0 +1,182 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* global describe, it, before, after */
+
+const assert = require('assert')
+const Immutable = require('immutable')
+const mockery = require('mockery')
+const fakeElectron = require('../../../../lib/fakeElectron')
+const {intersection} = require('../../../../../../app/renderer/components/styles/global')
+
+const frameKey = 1
+const index = 0
+let defaultState = Immutable.fromJS({
+ activeFrameKey: frameKey,
+ frames: [{
+ key: frameKey,
+ tabId: 1,
+ location: 'http://brave.com'
+ }],
+ tabs: [{
+ key: frameKey,
+ index: index
+ }],
+ framesInternal: {
+ index: { 1: 0 },
+ tabIndex: { 1: 0 }
+ }
+})
+
+describe('faviconState unit tests', function () {
+ let faviconState
+
+ before(function () {
+ mockery.enable({
+ warnOnReplace: false,
+ warnOnUnregistered: false,
+ useCleanCache: true
+ })
+ mockery.registerMock('electron', fakeElectron)
+ faviconState = require('../../../../../../app/common/state/tabContentState/faviconState')
+ })
+
+ after(function () {
+ mockery.deregisterAll()
+ mockery.disable()
+ })
+
+ describe('showFavicon', function () {
+ it('returns false if frame is null/undefined', function * () {
+ assert.equal(faviconState.showFavicon(), false)
+ })
+
+ it('returns true if tab is only 35% visible and is not active', function * () {
+ const state = defaultState
+ .set('activeFrameKey', 1337)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at40)
+ const result = faviconState.showFavicon(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns true if tab is not intercected and is not about:newtab', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.noIntersection)
+ const result = faviconState.showFavicon(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns false if tab is not intercected and is about:newtab', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'location'], 'about:newtab')
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.noIntersection)
+ const result = faviconState.showFavicon(state, frameKey)
+ assert.equal(result, false)
+ })
+ })
+
+ describe('getFavicon', function () {
+ it('returns false if frame is null/undefined', function * () {
+ assert.equal(faviconState.getFavicon(), false)
+ })
+
+ it('returns false if loading icon is visible', function * () {
+ const favicon = 'fred_water.png'
+ const state = defaultState.mergeIn(['frames', index], { loading: true, icon: favicon })
+ const result = faviconState.getFavicon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns the favicon if loading is not visible', function * () {
+ const favicon = 'fred_water_rlz.png'
+ const state = defaultState.mergeIn(['frames', index], { loading: false, icon: favicon })
+ const result = faviconState.getFavicon(state, frameKey)
+ assert.equal(result, favicon)
+ })
+ })
+
+ describe('showLoadingIcon', function () {
+ it('returns false if frame is null/undefined', function * () {
+ assert.equal(faviconState.showLoadingIcon(), false)
+ })
+
+ it('returns false if source is about page', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'location'], 'about:blank')
+ const result = faviconState.showLoadingIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns true if source is not about page', function * () {
+ const state = defaultState.setIn(['frames', index, 'loading'], true)
+ const result = faviconState.showLoadingIcon(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns false if page is not loading', function * () {
+ const state = defaultState.setIn(['frames', index, 'loading'], false)
+ const result = faviconState.showLoadingIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns false if loading is undefined', function * () {
+ const state = defaultState.setIn(['frames', index, 'loading'], undefined)
+ const result = faviconState.showLoadingIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns true if page is loading', function * () {
+ const state = defaultState.setIn(['frames', index, 'loading'], true)
+ const result = faviconState.showLoadingIcon(state, frameKey)
+ assert.equal(result, true)
+ })
+ })
+
+ describe('showIconWithLessMargin', function () {
+ it('returns false if frame is null/undefined', function * () {
+ assert.equal(faviconState.showIconWithLessMargin(), false)
+ })
+
+ it('returns true if tab is intersected at 20% size', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at30)
+ const result = faviconState.showIconWithLessMargin(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns true if tab is intersected at smaller size', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at20)
+ const result = faviconState.showIconWithLessMargin(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns false if tab is intersected at a larger size', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at40)
+ const result = faviconState.showIconWithLessMargin(state, frameKey)
+ assert.equal(result, false)
+ })
+ })
+
+ describe('showFaviconAtReducedSize', function () {
+ it('returns false if frame is null/undefined', function * () {
+ assert.equal(faviconState.showFaviconAtReducedSize(), false)
+ })
+
+ it('returns true if tab is intersected at 15% size', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at20)
+ const result = faviconState.showFaviconAtReducedSize(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns false if tab is intersected at larger size', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at30)
+ const result = faviconState.showFaviconAtReducedSize(state, frameKey)
+ assert.equal(result, false)
+ })
+ })
+})
diff --git a/test/unit/app/common/state/tabContentStateTest/partitionStateTest.js b/test/unit/app/common/state/tabContentStateTest/partitionStateTest.js
new file mode 100644
index 00000000000..dbef33f2352
--- /dev/null
+++ b/test/unit/app/common/state/tabContentStateTest/partitionStateTest.js
@@ -0,0 +1,118 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* global describe, it, before, after */
+
+const assert = require('assert')
+const Immutable = require('immutable')
+const mockery = require('mockery')
+const fakeElectron = require('../../../../lib/fakeElectron')
+const {tabs} = require('../../../../../../js/constants/config')
+
+const frameKey = 1
+const index = 0
+let defaultState = Immutable.fromJS({
+ activeFrameKey: frameKey,
+ frames: [{
+ key: frameKey,
+ tabId: 1,
+ location: 'http://brave.com'
+ }],
+ tabs: [{
+ key: frameKey,
+ index: index
+ }],
+ framesInternal: {
+ index: { 1: 0 },
+ tabIndex: { 1: 0 }
+ }
+})
+
+describe('partitionState unit tests', function () {
+ let partitionState
+
+ before(function () {
+ mockery.enable({
+ warnOnReplace: false,
+ warnOnUnregistered: false,
+ useCleanCache: true
+ })
+ mockery.registerMock('electron', fakeElectron)
+ partitionState = require('../../../../../../app/common/state/tabContentState/partitionState')
+ })
+
+ after(function () {
+ mockery.deregisterAll()
+ mockery.disable()
+ })
+
+ describe('isPartitionTab', function () {
+ it('returns false if frame is null/undefined', function () {
+ assert.equal(partitionState.isPartitionTab(), false)
+ })
+
+ it('returns true if partition number is defined', function * () {
+ const partitionNumber = 1337
+ const state = defaultState
+ .setIn(['frames', index, 'partitionNumber'], partitionNumber)
+ const result = partitionState.isPartitionTab(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns false if partition number is undefined', function * () {
+ const state = defaultState
+ const result = partitionState.isPartitionTab(state, frameKey)
+ assert.equal(result, false)
+ })
+ })
+
+ describe('getPartitionNumber', function () {
+ it('returns zero if frame is null/undefined', function * () {
+ assert.equal(partitionState.getPartitionNumber(), 0)
+ })
+
+ it('returns zero if frame is null/undefined', function * () {
+ assert.equal(partitionState.getPartitionNumber(), 0)
+ })
+
+ it('can remove _partition_ string and keep the partition number', function * () {
+ const partitionString = 'partition-9'
+ const partitionNumber = 9
+ const state = defaultState
+ .setIn(['frames', index, 'partitionNumber'], partitionString)
+ const result = partitionState.getPartitionNumber(state, frameKey)
+ assert.equal(result, partitionNumber)
+ })
+
+ it('returns the partition number', function * () {
+ const partitionNumber = 9
+ const state = defaultState
+ .setIn(['frames', index, 'partitionNumber'], partitionNumber)
+ const result = partitionState.getPartitionNumber(state, frameKey)
+ assert.equal(result, partitionNumber)
+ })
+ })
+
+ describe('getMaxAllowedPartitionNumber', function () {
+ it('returns false if frame is null/undefined', function () {
+ assert.equal(partitionState.getMaxAllowedPartitionNumber(), false)
+ })
+
+ it('returns partition number', function * () {
+ const partitionNumber = 9
+ const state = defaultState
+ .setIn(['frames', index, 'partitionNumber'], partitionNumber)
+ const result = partitionState.getMaxAllowedPartitionNumber(state, frameKey)
+ assert.equal(result, partitionNumber)
+ })
+
+ it('returns the max allowed partition number if current number is bigger', function * () {
+ const partitionNumber = 99999
+ const state = defaultState
+ .setIn(['frames', index, 'partitionNumber'], partitionNumber)
+ const result = partitionState.getMaxAllowedPartitionNumber(state, frameKey)
+ assert.equal(result, tabs.maxAllowedNewSessions)
+ })
+ })
+})
diff --git a/test/unit/app/common/state/tabContentStateTest/privateStateTest.js b/test/unit/app/common/state/tabContentStateTest/privateStateTest.js
new file mode 100644
index 00000000000..902d94108ae
--- /dev/null
+++ b/test/unit/app/common/state/tabContentStateTest/privateStateTest.js
@@ -0,0 +1,66 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* global describe, it, before, after */
+
+const assert = require('assert')
+const Immutable = require('immutable')
+const mockery = require('mockery')
+const fakeElectron = require('../../../../lib/fakeElectron')
+
+const frameKey = 1
+const index = 0
+let defaultState = Immutable.fromJS({
+ activeFrameKey: frameKey,
+ frames: [{
+ key: frameKey,
+ tabId: 1,
+ location: 'http://brave.com'
+ }],
+ tabs: [{
+ key: frameKey,
+ index: index
+ }],
+ framesInternal: {
+ index: { 1: 0 },
+ tabIndex: { 1: 0 }
+ }
+})
+
+describe('privateState unit tests', function () {
+ let privateState
+
+ before(function () {
+ mockery.enable({
+ warnOnReplace: false,
+ warnOnUnregistered: false,
+ useCleanCache: true
+ })
+ mockery.registerMock('electron', fakeElectron)
+ privateState = require('../../../../../../app/common/state/tabContentState/privateState')
+ })
+
+ after(function () {
+ mockery.deregisterAll()
+ mockery.disable()
+ })
+
+ describe('isPrivateTab', function () {
+ it('returns an empty string if frame is null/undefined', function () {
+ assert.equal(privateState.isPrivateTab(), false)
+ })
+
+ it('returns true if tab is private', function * () {
+ const state = defaultState.setIn(['frames', index, 'isPrivate'], true)
+ const result = privateState.isPrivateTab(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns false if tab is not private', function * () {
+ const state = defaultState.setIn(['frames', index, 'isPrivate'], false)
+ const result = privateState.isPrivateTab(state, frameKey)
+ assert.equal(result, false)
+ })
+ })
+})
diff --git a/test/unit/app/common/state/tabContentStateTest/titleStateTest.js b/test/unit/app/common/state/tabContentStateTest/titleStateTest.js
new file mode 100644
index 00000000000..2c45c294f93
--- /dev/null
+++ b/test/unit/app/common/state/tabContentStateTest/titleStateTest.js
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* global describe, it, before, after */
+
+const assert = require('assert')
+const Immutable = require('immutable')
+const mockery = require('mockery')
+const fakeElectron = require('../../../../lib/fakeElectron')
+const {intersection} = require('../../../../../../app/renderer/components/styles/global')
+
+const frameKey = 1
+const index = 0
+let defaultState = Immutable.fromJS({
+ activeFrameKey: frameKey,
+ frames: [{
+ key: frameKey,
+ tabId: 1,
+ location: 'http://brave.com'
+ }],
+ tabs: [{
+ key: frameKey,
+ index: index
+ }],
+ framesInternal: {
+ index: { 1: 0 },
+ tabIndex: { 1: 0 }
+ }
+})
+
+describe('titleState unit tests', function () {
+ let titleState
+
+ before(function () {
+ mockery.enable({
+ warnOnReplace: false,
+ warnOnUnregistered: false,
+ useCleanCache: true
+ })
+ mockery.registerMock('electron', fakeElectron)
+ mockery.registerMock('../../../../js/l10n', {
+ translation: () => 'wow such title very translated'
+ })
+ titleState = require('../../../../../../app/common/state/tabContentState/titleState')
+ })
+
+ after(function () {
+ mockery.deregisterAll()
+ mockery.disable()
+ })
+
+ describe('showTabTitle', function () {
+ it('returns an empty string if frame is null/undefined', function () {
+ assert.equal(titleState.showTabTitle(), false)
+ })
+
+ it('returns false if tab is intersected at 45% size and is active', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ const result = titleState.showTabTitle(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns false if tab is intersected at 45% size and is partitioned', function * () {
+ const state = defaultState
+ .set('activeFrameKey', 1337)
+ .setIn(['frames', index, 'partitionNumber'], 1)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ const result = titleState.showTabTitle(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns false if tab is intersected at 45% size and is private', function * () {
+ const state = defaultState
+ .set('activeFrameKey', 1337)
+ .setIn(['frames', index, 'isPrivate'], true)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ const result = titleState.showTabTitle(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns true if tab is intersected at 45% size and is about:newtab', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'location'], 'about:newtab')
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ const result = titleState.showTabTitle(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns true if tab is intersected at 45% size and has no secondary icon', function * () {
+ const state = defaultState
+ .set('activeFrameKey', 1337)
+ .setIn(['frames', index, 'isPrivate'], false)
+ .setIn(['frames', index, 'partitionNumber'], null)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ const result = titleState.showTabTitle(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns false if tab is intersected at 35% size', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at40)
+ const result = titleState.showTabTitle(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns true if tab is intersected above 35% size', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at60)
+ const result = titleState.showTabTitle(state, frameKey)
+ assert.equal(result, true)
+ })
+ })
+
+ describe('getDisplayTitle', function () {
+ it('returns an empty string if frame is null/undefined', function () {
+ assert.equal(titleState.getDisplayTitle(), false)
+ })
+
+ it('translates about:blank', function () {
+ const state = defaultState.setIn(['frames', index, 'location'], 'about:blank')
+ const result = titleState.getDisplayTitle(state, frameKey)
+ assert.equal(result, 'wow such title very translated')
+ })
+
+ it('translates about:newtab', function () {
+ const state = defaultState.setIn(['frames', index, 'location'], 'about:newtab')
+ const result = titleState.getDisplayTitle(state, frameKey)
+ assert.equal(result, 'wow such title very translated')
+ })
+
+ it('returns the title', function () {
+ const state = defaultState.setIn(['frames', index, 'title'], 'george clooney')
+ const result = titleState.getDisplayTitle(state, frameKey)
+ assert.equal(result, 'george clooney')
+ })
+
+ it('returns the location if title is not defined', function () {
+ const state = defaultState.mergeIn(['frames', index], {
+ title: '',
+ location: 'https://i-wouldnt-change-a-thing.com'
+ })
+ const result = titleState.getDisplayTitle(state, frameKey)
+ assert.equal(result, 'https://i-wouldnt-change-a-thing.com')
+ })
+
+ it('retuns an empty string if both title and location are not defined', function () {
+ const state = defaultState.mergeIn(['frames', index], {
+ title: '',
+ location: ''
+ })
+ const result = titleState.getDisplayTitle(state, frameKey)
+ assert.equal(result, '')
+ })
+ })
+})
diff --git a/test/unit/app/common/state/tabUIStateTest.js b/test/unit/app/common/state/tabUIStateTest.js
new file mode 100644
index 00000000000..fe9a9925e3e
--- /dev/null
+++ b/test/unit/app/common/state/tabUIStateTest.js
@@ -0,0 +1,408 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* global describe, it, before, beforeEach, after */
+
+const assert = require('assert')
+const Immutable = require('immutable')
+const mockery = require('mockery')
+const fakeElectron = require('../../../lib/fakeElectron')
+const {theme} = require('../../../../../app/renderer/components/styles/theme')
+const {intersection} = require('../../../../../app/renderer/components/styles/global')
+
+const frameKey = 1
+const index = 0
+let defaultState = Immutable.fromJS({
+ activeFrameKey: frameKey,
+ frames: [{
+ key: frameKey,
+ tabId: 1,
+ location: 'http://brave.com'
+ }],
+ tabs: [{
+ key: frameKey,
+ index: index
+ }],
+ framesInternal: {
+ index: { 1: 0 },
+ tabIndex: { 1: 0 }
+ }
+})
+
+describe('tabUIState unit tests', function () {
+ let tabUIState
+ let defaultValue
+
+ before(function () {
+ mockery.enable({
+ warnOnReplace: false,
+ warnOnUnregistered: false,
+ useCleanCache: true
+ })
+ mockery.registerMock('electron', fakeElectron)
+ mockery.registerMock('../../../js/settings', {
+ getSetting: () => defaultValue
+ })
+ tabUIState = require('../../../../../app/common/state/tabUIState')
+ })
+
+ beforeEach(function () {
+ defaultValue = true
+ })
+
+ after(function () {
+ mockery.deregisterAll()
+ mockery.disable()
+ })
+
+ describe('getThemeColor', function () {
+ it('returns an empty string if frame is null/undefined', function * () {
+ assert.equal(tabUIState.getThemeColor(), false)
+ })
+
+ it('returns the themeColor when PAINT_TABS is true', function * () {
+ const state = defaultState.setIn(['frames', index, 'themeColor'], '#c0ff33')
+ const result = tabUIState.getThemeColor(state, frameKey)
+ assert.equal(result, '#c0ff33')
+ })
+
+ it('returns computedThemeColor when PAINT_TABS is true and themeColor is empty', function * () {
+ const state = defaultState.mergeIn(['frames', index], {
+ themeColor: '',
+ computedThemeColor: 'saddlebrown'
+ })
+ const result = tabUIState.getThemeColor(state, frameKey)
+ assert.equal(result, 'saddlebrown')
+ })
+
+ it('returns false when PAINT_TABS is false', function * () {
+ defaultValue = false
+ const state = defaultState.mergeIn(['frames', index], {
+ themeColor: '#c0ff33',
+ computedThemeColor: 'saddlebrown'
+ })
+ const result = tabUIState.getThemeColor(state, frameKey)
+ assert.equal(result, false)
+ })
+ })
+
+ describe('getTabIconColor', function () {
+ it('returns an empty string if frame is null/undefined', function * () {
+ assert.equal(tabUIState.getTabIconColor(), false)
+ })
+
+ it('returns black if tab background is lighter, has themeColor, paintTabs is enabled and is active but not private', function * () {
+ const state = defaultState.mergeIn(['frames', index], {
+ themeColor: '#fff',
+ isPrivate: false
+ })
+ const result = tabUIState.getTabIconColor(state, frameKey)
+ assert.equal(result, 'black')
+ })
+
+ it('returns black if tab background is darker, has themeColor, paintTabs is enabled and is active but not private', function * () {
+ const state = defaultState.mergeIn(['frames', index], {
+ themeColor: '#000',
+ isPrivate: false
+ })
+ const result = tabUIState.getTabIconColor(state, frameKey)
+ assert.equal(result, 'white')
+ })
+
+ it('returns white if tab is active and private', function * () {
+ const state = defaultState.mergeIn(['frames', index], {
+ themeColor: '#fff',
+ isPrivate: true
+ })
+ const result = tabUIState.getTabIconColor(state, frameKey)
+ assert.equal(result, 'white')
+ })
+
+ it('returns black if tab is active, not private but has no themeColor', function * () {
+ const state = defaultState.mergeIn(['frames', index], {
+ themeColor: false,
+ isPrivate: false
+ })
+ const result = tabUIState.getTabIconColor(state, frameKey)
+ assert.equal(result, 'black')
+ })
+ })
+
+ describe('checkIfTextColor', function () {
+ it('returns an empty string if frame is null/undefined', function * () {
+ assert.equal(tabUIState.checkIfTextColor(), false)
+ })
+
+ it('returns true if colors match', function * () {
+ const state = defaultState.mergeIn(['frames', index], {
+ themeColor: false,
+ isPrivate: false
+ })
+ const result = tabUIState.checkIfTextColor(state, frameKey, 'black')
+ assert.equal(result, true)
+ })
+
+ it('returns false if colors does not match', function * () {
+ const state = defaultState.mergeIn(['frames', index], {
+ themeColor: false,
+ isPrivate: true
+ })
+ const result = tabUIState.checkIfTextColor(state, frameKey, 'black')
+ assert.equal(result, false)
+ })
+ })
+
+ describe('showTabEndIcon', function () {
+ it('returns false if frame is null/undefined', function * () {
+ assert.equal(tabUIState.showTabEndIcon(), false)
+ })
+
+ it('returns false for regular tabs', function * () {
+ const state = defaultState.mergeIn(['frames', index], {
+ isPrivate: false,
+ partitionNumber: 0
+ })
+ const result = tabUIState.showTabEndIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns false for regular tabs', function * () {
+ const state = defaultState.mergeIn(['frames', index], {
+ isPrivate: false,
+ partitionNumber: false
+ })
+ const result = tabUIState.showTabEndIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ describe('when tab is partitioned', function () {
+ it('returns false if intersection is above 35% of tab size and has relative close icon', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'partitionNumber'], 1337)
+ .mergeIn(['ui', 'tabs'], {
+ intersectionRatio: intersection.at75,
+ hoverTabIndex: index
+ })
+ const result = tabUIState.showTabEndIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns false if intersection is above 35% of tab size and has fixed close icon', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'partitionNumber'], 1337)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at75)
+ const result = tabUIState.showTabEndIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns false if intersection is below 35% of tab size', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'partitionNumber'], 1337)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at40)
+ const result = tabUIState.showTabEndIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns true if not hovering and intersection is above 35% of tab size', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'partitionNumber'], 1337)
+ .mergeIn(['ui', 'tabs'], {
+ intersectionRatio: intersection.noIntersection,
+ hoverTabIndex: 123123
+ })
+ const result = tabUIState.showTabEndIcon(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns true if not active and intersection is above 35% of tab size', function * () {
+ const state = defaultState
+ .set('activeFrameKey', 1337)
+ .setIn(['frames', index, 'partitionNumber'], 1337)
+ .mergeIn(['ui', 'tabs'], {
+ intersectionRatio: intersection.noIntersection
+ })
+ const result = tabUIState.showTabEndIcon(state, frameKey)
+ assert.equal(result, true)
+ })
+ })
+
+ describe('when tab is private', function () {
+ it('returns false if intersection is above 35% of tab size and has relative close icon', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'isPrivate'], true)
+ .mergeIn(['ui', 'tabs'], {
+ intersectionRatio: intersection.at75,
+ hoverTabIndex: index
+ })
+ const result = tabUIState.showTabEndIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns false if intersection is above 35% of tab size and has fixed close icon', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'isPrivate'], true)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at75)
+ const result = tabUIState.showTabEndIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns false if intersection is below 35% of tab size', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'isPrivate'], true)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at40)
+ const result = tabUIState.showTabEndIcon(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns true if not hovering and intersection is above 35% of tab size', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'isPrivate'], true)
+ .mergeIn(['ui', 'tabs'], {
+ intersectionRatio: intersection.noIntersection,
+ hoverTabIndex: 123123
+ })
+ const result = tabUIState.showTabEndIcon(state, frameKey)
+ assert.equal(result, true)
+ })
+
+ it('returns true if not active and intersection is above 35% of tab size', function * () {
+ const state = defaultState
+ .set('activeFrameKey', 1337)
+ .setIn(['frames', index, 'isPrivate'], true)
+ .mergeIn(['ui', 'tabs'], {
+ intersectionRatio: intersection.noIntersection
+ })
+ const result = tabUIState.showTabEndIcon(state, frameKey)
+ assert.equal(result, true)
+ })
+ })
+ })
+
+ describe('addExtraGutterToTitle', function () {
+ it('returns false if frame is null/undefined', function * () {
+ assert.equal(tabUIState.addExtraGutterToTitle(), false)
+ })
+ it('returns true for about:newtab', function * () {
+ const state = defaultState.setIn(['frames', index, 'location'], 'about:newtab')
+ const result = tabUIState.addExtraGutterToTitle(state, frameKey)
+ assert.equal(result, true)
+ })
+ it('returns false for other locations', function * () {
+ const state = defaultState.setIn(['frames', index, 'location'], 'whatelse.com')
+ const result = tabUIState.addExtraGutterToTitle(state, frameKey)
+ assert.equal(result, false)
+ })
+ })
+
+ describe('centralizeTabIcons', function () {
+ it('returns false if frame is null/undefined', function * () {
+ assert.equal(tabUIState.centralizeTabIcons(), false)
+ })
+
+ it('returns false if intersection is above 15% of tab size', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ const result = tabUIState.centralizeTabIcons(state, frameKey)
+ assert.equal(result, false)
+ })
+
+ it('returns true if intersection is below or equal 15% of tab size', function * () {
+ const state = defaultState
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at20)
+ const result = tabUIState.centralizeTabIcons(state, frameKey)
+ assert.equal(result, true)
+ })
+ })
+
+ describe('getTabEndIconBackgroundColor', function () {
+ before(function () {
+ // just a helper for results
+ this.defaultResult = (bgColor, color1Size, color2Size) =>
+ `linear-gradient(to left, ${bgColor} ${color1Size}, transparent ${color2Size})`
+ })
+
+ describe('when tab is private', function () {
+ it('returns `tab.private.background` color if not active', function * () {
+ const state = defaultState
+ .set('activeFrameKey', 1337)
+ .mergeIn(['frames', index], {
+ themeColor: '#c0ff33',
+ isPrivate: true
+ })
+ const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey)
+ const expected = this.defaultResult(theme.tab.private.background, '10px', '40px')
+ assert.equal(result, expected)
+ })
+
+ it('returns `tab.active.private.background` if tab is active', function * () {
+ const state = defaultState
+ .mergeIn(['frames', index], {
+ themeColor: '#c0ff33',
+ isPrivate: true
+ })
+ const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey)
+ const expected = this.defaultResult(theme.tab.active.private.background, '10px', '40px')
+ assert.equal(result, expected)
+ })
+
+ it('retuns active private color if tab is being hovered', function * () {
+ const state = defaultState
+ .mergeIn(['frames', index], {
+ themeColor: '#c0ff33',
+ isPrivate: true
+ })
+ .setIn(['ui', 'tabs', 'hoverTabIndex'], index)
+ const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey)
+ const expected = this.defaultResult(theme.tab.active.private.background, '10px', '40px')
+ assert.equal(result, expected)
+ })
+ })
+
+ describe('when tab is not private', function () {
+ it('returns the themeColor if tab is active', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'themeColor'], '#c0ff33')
+ const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey)
+ const expected = this.defaultResult('#c0ff33', '0', '12px')
+ assert.equal(result, expected)
+ })
+ it('returns `theme.tab.background` if tab is not active', function * () {
+ const state = defaultState
+ .set('activeFrameKey', 1337)
+ .setIn(['frames', index, 'themeColor'], '#c0ff33')
+ const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey)
+ const expected = this.defaultResult(theme.tab.background, '0', '12px')
+ assert.equal(result, expected)
+ })
+ })
+
+ describe('returns `linear gradient` size', function () {
+ it('at 10px/40px if tab is partitioned', function * () {
+ const state = defaultState
+ .mergeIn(['frames', index], {
+ partitionNumber: 1337,
+ themeColor: '#c0ff33'
+ })
+ const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey)
+ const expected = this.defaultResult('#c0ff33', '10px', '40px')
+ assert.equal(result, expected)
+ })
+ it('at 10px/40px gradient size if tab has a visible close icon', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'themeColor'], '#c0ff33')
+ .setIn(['ui', 'tabs', 'hoverTabIndex'], index)
+ const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey)
+ const expected = this.defaultResult('#c0ff33', '10px', '40px')
+ assert.equal(result, expected)
+ })
+ it('at 0/12px gradient size if is neither private, partition or has close icon visible', function * () {
+ const state = defaultState
+ .setIn(['frames', index, 'themeColor'], '#c0ff33')
+ const result = tabUIState.getTabEndIconBackgroundColor(state, frameKey)
+ const expected = this.defaultResult('#c0ff33', '0', '12px')
+ assert.equal(result, expected)
+ })
+ })
+ })
+})
diff --git a/test/unit/app/renderer/components/tabs/content/audioTabIconTest.js b/test/unit/app/renderer/components/tabs/content/audioTabIconTest.js
index c142fbb8ef4..98ba8a7a5e9 100644
--- a/test/unit/app/renderer/components/tabs/content/audioTabIconTest.js
+++ b/test/unit/app/renderer/components/tabs/content/audioTabIconTest.js
@@ -1,36 +1,37 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
-/* global describe, before, it, after */
+/* global describe, before, after, it */
const mockery = require('mockery')
const {mount} = require('enzyme')
const assert = require('assert')
const Immutable = require('immutable')
-const globalStyles = require('../../../../../../../app/renderer/components/styles/global')
const fakeElectron = require('../../../../../lib/fakeElectron')
+const globalStyles = require('../../../../../../../app/renderer/components/styles/global')
require('../../../../../braveUnit')
+const index = 0
const tabId = 1
const frameKey = 1
-const invalidFrameKey = 71
-const fakeAppStoreRenderer = {
- state: Immutable.fromJS({
- windows: [{
- windowId: 1,
- windowUUID: 'uuid'
- }],
- tabs: [{
- tabId: tabId,
- windowId: 1,
- windowUUID: 'uuid',
- url: 'https://brave.com'
- }]
- }),
- addChangeListener: () => {},
- removeChangeListener: () => {}
-}
+const fakeAppStoreRenderer = Immutable.fromJS({
+ windows: [{
+ windowId: 1,
+ windowUUID: 'uuid'
+ }],
+ tabs: [{
+ tabId: tabId,
+ windowId: 1,
+ windowUUID: 'uuid',
+ url: 'https://brave.com'
+ }],
+ tabsInternal: {
+ index: {
+ 1: 0
+ }
+ }
+})
const defaultWindowStore = Immutable.fromJS({
activeFrameKey: frameKey,
@@ -40,7 +41,8 @@ const defaultWindowStore = Immutable.fromJS({
location: 'http://brave.com'
}],
tabs: [{
- key: frameKey
+ key: frameKey,
+ index: index
}],
framesInternal: {
index: {
@@ -53,7 +55,7 @@ const defaultWindowStore = Immutable.fromJS({
})
describe('Tabs content - AudioTabIcon', function () {
- let Tab, windowStore
+ let AudioTabIcon, windowStore, appStore
before(function () {
mockery.enable({
@@ -62,14 +64,10 @@ describe('Tabs content - AudioTabIcon', function () {
useCleanCache: true
})
mockery.registerMock('electron', fakeElectron)
- mockery.registerMock('../../../js/stores/appStoreRenderer', fakeAppStoreRenderer)
- mockery.registerMock('../../../../extensions/brave/img/tabs/loading.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/new_session.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/private.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/close_btn_hover.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/close_btn_normal.svg')
windowStore = require('../../../../../../../js/stores/windowStore')
- Tab = require('../../../../../../../app/renderer/components/tabs/tab')
+ appStore = require('../../../../../../../js/stores/appStoreRenderer')
+ AudioTabIcon = require('../../../../../../../app/renderer/components/tabs/content/audioTabIcon')
+ appStore.state = fakeAppStoreRenderer
})
after(function () {
@@ -77,62 +75,43 @@ describe('Tabs content - AudioTabIcon', function () {
mockery.disable()
})
- describe('should show', function () {
- it('play icon if page has audio enabled', function () {
+ describe('should show icon', function () {
+ it('volumeOn if page has audio enabled', function * () {
windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- audioPlaybackActive: true,
- breakpoint: 'default'
+ audioPlaybackActive: true
})
- const wrapper = mount()
- assert.equal(wrapper.find('AudioTabIcon TabIcon').props().symbol, globalStyles.appIcons.volumeOn)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').props()['data-test-id'], globalStyles.appIcons.volumeOn)
})
- it('mute icon if page has audio muted', function () {
+ it('volumeOff if page has audio muted', function * () {
windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
audioPlaybackActive: true,
- audioMuted: true,
- breakpoint: 'default'
+ audioMuted: true
})
- const wrapper = mount()
- assert.equal(wrapper.find('AudioTabIcon TabIcon').props().symbol, globalStyles.appIcons.volumeOff)
- })
- it('passing in a frame key which does not exist does not fail', function () {
- windowStore.state = defaultWindowStore
- const wrapper = mount()
- // No audio icon is rendered in this case so just check for Tab
- assert(wrapper.find('Tab'))
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').props()['data-test-id'], globalStyles.appIcons.volumeOff)
})
})
- describe('should not show', function () {
- it('any audio icon if page has audio disabled', function () {
+ describe('should not show icon', function () {
+ it('if page has audio disabled', function * () {
windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
audioPlaybackActive: false,
- breakpoint: 'default'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('AudioTabIcon').length, 0)
- assert.equal(wrapper.find('AudioTabIcon').length, 0)
- })
-
- it('play audio icon if tab size is different than default', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- audioPlaybackActive: true,
- audioMuted: false,
- breakpoint: 'small'
+ audioMuted: false
})
- const wrapper = mount()
- assert.equal(wrapper.find('AudioTabIcon').length, 0)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 0)
})
- it('mute icon if tab size is different than default', function () {
+ it('if tab is intersected', function * () {
windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
audioPlaybackActive: true,
- audioMuted: true,
- breakpoint: 'small'
+ audioMuted: false
})
- const wrapper = mount()
- assert.equal(wrapper.find('AudioTabIcon').length, 0)
+ windowStore.state = defaultWindowStore.setIn(['ui', 'tabs', 'intersection'], 0)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 0)
})
})
})
diff --git a/test/unit/app/renderer/components/tabs/content/closeTabIconTest.js b/test/unit/app/renderer/components/tabs/content/closeTabIconTest.js
index 85455b8432b..716eae7fb54 100644
--- a/test/unit/app/renderer/components/tabs/content/closeTabIconTest.js
+++ b/test/unit/app/renderer/components/tabs/content/closeTabIconTest.js
@@ -8,29 +8,12 @@ const {mount} = require('enzyme')
const assert = require('assert')
const Immutable = require('immutable')
const fakeElectron = require('../../../../../lib/fakeElectron')
+const {intersection} = require('../../../../../../../app/renderer/components/styles/global')
require('../../../../../braveUnit')
const index = 0
const tabId = 1
const frameKey = 1
-const invalidFrameKey = 71
-
-const fakeAppStoreRenderer = {
- state: Immutable.fromJS({
- windows: [{
- windowId: 1,
- windowUUID: 'uuid'
- }],
- tabs: [{
- tabId: tabId,
- windowId: 1,
- windowUUID: 'uuid',
- url: 'https://brave.com'
- }]
- }),
- addChangeListener: () => {},
- removeChangeListener: () => {}
-}
const defaultWindowStore = Immutable.fromJS({
activeFrameKey: frameKey,
@@ -50,11 +33,6 @@ const defaultWindowStore = Immutable.fromJS({
tabIndex: {
1: 0
}
- },
- ui: {
- tabs: {
- hoverTabIndex: index
- }
}
})
@@ -68,9 +46,7 @@ describe('Tabs content - CloseTabIcon', function () {
useCleanCache: true
})
mockery.registerMock('electron', fakeElectron)
- mockery.registerMock('../../../js/stores/appStoreRenderer', fakeAppStoreRenderer)
- mockery.registerMock('../../../../extensions/brave/img/tabs/close_btn_hover.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/close_btn_normal.svg')
+ mockery.registerMock('../../../../extensions/brave/img/tabs/close_btn.svg')
windowStore = require('../../../../../../../js/stores/windowStore')
CloseTabIcon = require('../../../../../../../app/renderer/components/tabs/content/closeTabIcon')
})
@@ -81,154 +57,46 @@ describe('Tabs content - CloseTabIcon', function () {
})
describe('should show icon', function () {
- it('if mouse is over tab and breakpoint is default', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- hoverState: true,
- breakpoint: 'default'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props()['data-test2-id'], 'close-icon-on')
- })
-
- it('if mouse is over tab and breakpoint is large', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- hoverState: true,
- breakpoint: 'large'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props()['data-test2-id'], 'close-icon-on')
- })
-
- it('if tab size is largeMedium and tab is active', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- hoverState: false,
- breakpoint: 'largeMedium'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props()['data-test2-id'], 'close-icon-on')
- })
-
- it('if tab size is medium and tab is active', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- hoverState: false,
- breakpoint: 'medium'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props()['data-test2-id'], 'close-icon-on')
- })
-
- it('if tab size is mediumSmall and tab is active', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- hoverState: false,
- breakpoint: 'mediumSmall'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props()['data-test2-id'], 'close-icon-on')
- })
-
- it('if tab size is small and tab is active', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- hoverState: false,
- breakpoint: 'small'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props()['data-test2-id'], 'close-icon-on')
- })
-
- it('if tab size is extraSmall and tab is active', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- hoverState: false,
- breakpoint: 'extraSmall'
+ it('if not intersected and tab is hovered', function * () {
+ windowStore.state = defaultWindowStore.mergeIn(['ui', 'tabs'], {
+ intersectionRatio: intersection.noIntersection,
+ hoverTabIndex: index
})
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props()['data-test2-id'], 'close-icon-on')
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 1)
})
- it('passing in a frame key which does not exist does not fail', function () {
+ it('if intersection is at less than 75% size and tab is active', function * () {
windowStore.state = defaultWindowStore
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props()['data-test2-id'], 'close-icon-off')
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at75)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 1)
})
})
describe('should not show icon', function () {
- it('if tab is pinned', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- hoverState: true,
- pinnedLocation: true
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props()['data-test2-id'], 'close-icon-off')
- })
-
- it('if tab size is largeMedium and tab is not active', function () {
- windowStore.state = defaultWindowStore.merge({
- activeFrameKey: 0,
- frames: [{
- hoverState: true,
- breakpoint: 'largeMedium'
- }]
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props()['data-test2-id'], 'close-icon-off')
- })
-
- it('if tab size is medium and tab is not active', function () {
- windowStore.state = defaultWindowStore.merge({
- activeFrameKey: 0,
- frames: [{
- hoverState: true,
- breakpoint: 'medium'
- }]
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props()['data-test2-id'], 'close-icon-off')
- })
-
- it('if tab size is mediumSmall and tab is not active', function () {
- windowStore.state = defaultWindowStore.merge({
- activeFrameKey: 0,
- frames: [{
- hoverState: true,
- breakpoint: 'mediumSmall'
- }]
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props()['data-test2-id'], 'close-icon-off')
- })
-
- it('if tab size is small and tab is not active', function () {
- windowStore.state = defaultWindowStore.merge({
- activeFrameKey: 0,
- frames: [{
- hoverState: true,
- breakpoint: 'small'
- }]
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props()['data-test2-id'], 'close-icon-off')
+ it('if tab is intersected at 20% size or less', function * () {
+ windowStore.state = defaultWindowStore
+ .setIn(['ui', 'tabs', 'intersectionRatio', intersection.at20])
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 0)
})
- it('if tab size is extraSmall and tab is not active', function () {
- windowStore.state = defaultWindowStore.merge({
- activeFrameKey: 0,
- frames: [{
- hoverState: true,
- breakpoint: 'extraSmall'
- }]
+ it('if not intersected and tab is not hovered', function * () {
+ windowStore.state = defaultWindowStore.mergeIn(['ui', 'tabs'], {
+ intersectionRatio: intersection.noIntersection,
+ hoverTabIndex: 1337
})
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props()['data-test2-id'], 'close-icon-off')
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 0)
})
- // TODO check what is going on
- it.skip('if tab size is the smallest size', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- hoverState: true,
- breakpoint: 'extraSmall'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props()['data-test2-id'], 'close-icon-off')
+ it('if intersection is at less than 75% size and tab is not active', function * () {
+ windowStore.state = defaultWindowStore
+ .set('activeFrameKey', 1337)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 0)
})
})
})
diff --git a/test/unit/app/renderer/components/tabs/content/favIconTest.js b/test/unit/app/renderer/components/tabs/content/favIconTest.js
index 8dcce4144ac..75d62d9603f 100644
--- a/test/unit/app/renderer/components/tabs/content/favIconTest.js
+++ b/test/unit/app/renderer/components/tabs/content/favIconTest.js
@@ -5,34 +5,32 @@
const mockery = require('mockery')
const {mount} = require('enzyme')
-const Immutable = require('immutable')
const assert = require('assert')
-const globalStyles = require('../../../../../../../app/renderer/components/styles/global')
+const Immutable = require('immutable')
const fakeElectron = require('../../../../../lib/fakeElectron')
require('../../../../../braveUnit')
-const url1 = 'https://brave.com'
-const favicon1 = 'https://brave.com/favicon.ico'
-
+const index = 0
const tabId = 1
const frameKey = 1
-const fakeAppStoreRenderer = {
- state: Immutable.fromJS({
- windows: [{
- windowId: 1,
- windowUUID: 'uuid'
- }],
- tabs: [{
- tabId: tabId,
- windowId: 1,
- windowUUID: 'uuid',
- url: 'https://brave.com'
- }]
- }),
- addChangeListener: () => {},
- removeChangeListener: () => {}
-}
+const fakeAppStoreRenderer = Immutable.fromJS({
+ windows: [{
+ windowId: 1,
+ windowUUID: 'uuid'
+ }],
+ tabs: [{
+ tabId: tabId,
+ windowId: 1,
+ windowUUID: 'uuid',
+ url: 'https://brave.com'
+ }],
+ tabsInternal: {
+ index: {
+ 1: 0
+ }
+ }
+})
const defaultWindowStore = Immutable.fromJS({
activeFrameKey: frameKey,
@@ -42,7 +40,8 @@ const defaultWindowStore = Immutable.fromJS({
location: 'http://brave.com'
}],
tabs: [{
- key: frameKey
+ key: frameKey,
+ index: index
}],
framesInternal: {
index: {
@@ -55,7 +54,7 @@ const defaultWindowStore = Immutable.fromJS({
})
describe('Tabs content - Favicon', function () {
- let Tab, windowStore
+ let Favicon, windowStore, appStore
before(function () {
mockery.enable({
@@ -64,17 +63,12 @@ describe('Tabs content - Favicon', function () {
useCleanCache: true
})
mockery.registerMock('electron', fakeElectron)
- mockery.registerMock('../../../js/l10n', {
- translation: () => 'translated'
- })
- mockery.registerMock('../../../js/stores/appStoreRenderer', fakeAppStoreRenderer)
+ mockery.registerMock('../../../../extensions/brave/img/tabs/default.svg')
mockery.registerMock('../../../../extensions/brave/img/tabs/loading.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/new_session.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/private.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/close_btn_hover.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/close_btn_normal.svg')
windowStore = require('../../../../../../../js/stores/windowStore')
- Tab = require('../../../../../../../app/renderer/components/tabs/tab')
+ appStore = require('../../../../../../../js/stores/appStoreRenderer')
+ Favicon = require('../../../../../../../app/renderer/components/tabs/content/favicon')
+ appStore.state = fakeAppStoreRenderer
})
after(function () {
@@ -82,43 +76,71 @@ describe('Tabs content - Favicon', function () {
mockery.disable()
})
- describe('should show', function () {
- it('favicon if page has one', function () {
+ describe('loading icon', function () {
+ it('shows when tab is loading', function * () {
+ windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
+ icon: 'winter-is-coming.jpg',
+ loading: true
+ })
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').props()['data-test-id'], 'loading')
+ })
+ it('does not show when tab is not loading', function * () {
windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- location: url1,
- icon: favicon1,
+ icon: 'winter-is-coming.jpg',
loading: false
})
- const wrapper = mount()
- assert.equal(wrapper.find('Favicon').length, 1)
+ const wrapper = mount()
+ assert.notEqual(wrapper.find('TabIcon').props()['data-test-id'], 'loading')
+ assert.equal(wrapper.find('TabIcon').props()['data-test-id'], 'winter-is-coming.jpg')
})
-
- it('placeholder icon if page has no favicon', function () {
+ })
+ describe('default icon', function () {
+ it('shows when tab has no icon', function * () {
windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- location: url1,
icon: null,
loading: false
})
- const wrapper = mount()
- assert.equal(wrapper.find('Favicon TabIcon').props().symbol, globalStyles.appIcons.defaultIcon)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').props()['data-test-id'], 'defaultIcon')
+ })
+ it('does not show when tab has an icon', function * () {
+ windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
+ icon: 'the-night-is-dark-and-full-of-terror.jpg',
+ loading: false
+ })
+ const wrapper = mount()
+ assert.notEqual(wrapper.find('TabIcon').props()['data-test-id'], 'defaultIcon')
+ })
+ })
+ describe('favicon', function () {
+ it('shows if page has a favicon', function * () {
+ windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
+ icon: 'bbondy-king-of-the-north.jpg',
+ loading: false
+ })
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').props()['data-test-id'], 'bbondy-king-of-the-north.jpg')
})
- it('loading icon if page is still loading', function () {
+ it('does not show if page is loading', function * () {
windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- location: url1,
- icon: favicon1,
+ icon: 'iron-throne-belongs-to-serg.jpg',
loading: true
})
- const wrapper = mount()
- assert.equal(wrapper.find('Favicon TabIcon').props()['data-test-id'], 'loading')
+ const wrapper = mount()
+ assert.notEqual(wrapper.find('TabIcon').props()['data-test-id'], 'iron-throne-belongs-to-serg.jpg')
+ assert.equal(wrapper.find('TabIcon').props()['data-test-id'], 'loading')
})
- })
- it('should not show favicon for new tab page', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- location: 'about:newtab'
+ it('does not show if page has no favicon', function * () {
+ windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
+ icon: null,
+ loading: false
+ })
+ const wrapper = mount()
+ assert.notEqual(wrapper.find('TabIcon').props()['data-test-id'], null)
+ assert.equal(wrapper.find('TabIcon').props()['data-test-id'], 'defaultIcon')
})
- const wrapper = mount()
- assert.equal(wrapper.find('Favicon').length, 0)
})
})
diff --git a/test/unit/app/renderer/components/tabs/content/newSessionIconTest.js b/test/unit/app/renderer/components/tabs/content/newSessionIconTest.js
index e0aac5db86f..24470da8fdb 100644
--- a/test/unit/app/renderer/components/tabs/content/newSessionIconTest.js
+++ b/test/unit/app/renderer/components/tabs/content/newSessionIconTest.js
@@ -7,38 +7,39 @@ const mockery = require('mockery')
const {mount} = require('enzyme')
const assert = require('assert')
const Immutable = require('immutable')
-const {tabs} = require('../../../../../../../js/constants/config')
const fakeElectron = require('../../../../../lib/fakeElectron')
+const {intersection} = require('../../../../../../../app/renderer/components/styles/global')
require('../../../../../braveUnit')
const index = 0
const tabId = 1
const frameKey = 1
-const invalidFrameKey = 71
-const fakeAppStoreRenderer = {
- state: Immutable.fromJS({
- windows: [{
- windowId: 1,
- windowUUID: 'uuid'
- }],
- tabs: [{
- tabId: tabId,
- windowId: 1,
- windowUUID: 'uuid',
- url: 'https://brave.com'
- }]
- }),
- addChangeListener: () => {},
- removeChangeListener: () => {}
-}
+const fakeAppStoreRenderer = Immutable.fromJS({
+ windows: [{
+ windowId: 1,
+ windowUUID: 'uuid'
+ }],
+ tabs: [{
+ tabId: tabId,
+ windowId: 1,
+ windowUUID: 'uuid',
+ url: 'https://brave.com'
+ }],
+ tabsInternal: {
+ index: {
+ 1: 0
+ }
+ }
+})
const defaultWindowStore = Immutable.fromJS({
activeFrameKey: frameKey,
frames: [{
key: frameKey,
tabId: tabId,
- location: 'http://brave.com'
+ location: 'http://brave.com',
+ partitionNumber: 1
}],
tabs: [{
key: frameKey,
@@ -54,13 +55,13 @@ const defaultWindowStore = Immutable.fromJS({
},
ui: {
tabs: {
- hoverTabIndex: index
+ tabHoverState: 1
}
}
})
describe('Tabs content - NewSessionIcon', function () {
- let Tab, windowStore, NewSessionIcon
+ let NewSessionIcon, windowStore, appStore
before(function () {
mockery.enable({
@@ -69,15 +70,11 @@ describe('Tabs content - NewSessionIcon', function () {
useCleanCache: true
})
mockery.registerMock('electron', fakeElectron)
- mockery.registerMock('../../../js/stores/appStoreRenderer', fakeAppStoreRenderer)
- mockery.registerMock('../../../../extensions/brave/img/tabs/loading.svg')
mockery.registerMock('../../../../extensions/brave/img/tabs/new_session.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/private.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/close_btn_hover.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/close_btn_normal.svg')
windowStore = require('../../../../../../../js/stores/windowStore')
- Tab = require('../../../../../../../app/renderer/components/tabs/tab')
- NewSessionIcon = require('../../../../../../../app/renderer/components/tabs/content/newSessionIcon')
+ appStore = require('../../../../../../../js/stores/appStoreRenderer')
+ NewSessionIcon = require('../../../../../../../app/renderer/components/tabs/content/NewSessionIcon')
+ appStore.state = fakeAppStoreRenderer
})
after(function () {
@@ -85,195 +82,63 @@ describe('Tabs content - NewSessionIcon', function () {
mockery.disable()
})
- describe('should show', function () {
- it('icon if current tab is a new session tab', function () {
- windowStore.state = defaultWindowStore.merge({
- activeFrameKey: 0,
- frames: [{
- partitionNumber: 1,
- breakpoint: 'default'
- }],
- ui: {
- tabs: {
- hoverTabIndex: null
- }
- }
- })
- const wrapper = mount()
- assert.equal(wrapper.find('NewSessionIcon').length, 1)
- })
-
- it('icon if mouse is not over tab and breakpoint is default', function () {
- windowStore.state = defaultWindowStore.merge({
- activeFrameKey: 0,
- frames: [{
- partitionNumber: 1,
- breakpoint: 'default'
- }],
- ui: {
- tabs: {
- hoverTabIndex: null
- }
- }
- })
- const wrapper = mount()
- assert.equal(wrapper.find('NewSessionIcon').length, 1)
- })
-
- it('icon if mouse is not over tab and breakpoint is large', function () {
- windowStore.state = defaultWindowStore.merge({
- activeFrameKey: 0,
- frames: [{
- partitionNumber: 1,
- breakpoint: 'large'
- }],
- ui: {
- tabs: {
- hoverTabIndex: null
- }
- }
- })
- const wrapper = mount()
- assert.equal(wrapper.find('NewSessionIcon').length, 1)
- })
-
- it('icon if tab is not active and breakpoint is largeMedium', function () {
- windowStore.state = defaultWindowStore.merge({
- activeFrameKey: 0,
- frames: [{
- partitionNumber: 1,
- breakpoint: 'largeMedium'
- }],
- ui: {
- tabs: {
- hoverTabIndex: null
- }
- }
- })
- const wrapper = mount()
- assert.equal(wrapper.find('NewSessionIcon').length, 1)
- })
-
- it('partition number for new sessions', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- partitionNumber: 3,
- breakpoint: 'default'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props().symbolContent, 3)
- })
-
- it('partition number for sessions with number set by opener (ex: clicking target=_blank)', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- partitionNumber: 'partition-3',
- breakpoint: 'default'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props().symbolContent, 3)
+ describe('should show icon', function () {
+ it('if tab is not hovered', function * () {
+ windowStore.state = defaultWindowStore
+ .mergeIn(['ui', 'tabs'], {
+ intersectionRatio: intersection.noIntersection,
+ tabHoverIndex: 1337
+ })
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 1)
})
- it('max partition number even if session is bigger', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- partitionNumber: 1000,
- breakpoint: 'default'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabIcon').props().symbolContent, tabs.maxAllowedNewSessions)
- })
- it('passing in a frame key which does not exist does not fail', function () {
+ it('if tab is not active and size is small', function * () {
windowStore.state = defaultWindowStore
- const wrapper = mount()
- assert(wrapper.find('TabIcon'))
+ .mergeIn(['ui', 'tabs'], {
+ tabHoverIndex: 1337,
+ intersectionRatio: intersection.at45
+ })
+ .set('activeFrameKey', 1337)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 1)
})
})
describe('should not show icon', function () {
- it('if current tab is not private', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- partitionNumber: false
- })
- const wrapper = mount()
- assert.equal(wrapper.find('NewSessionIcon').length, 0)
- })
-
- it('if mouse is over tab and breakpoint is default', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- partitionNumber: 1,
- hoverState: true,
- breakpoint: 'default'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('NewSessionIcon').length, 0)
- })
-
- it('if mouse is over tab and breakpoint is large', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- partitionNumber: 1,
- hoverState: true,
- breakpoint: 'large'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('NewSessionIcon').length, 0)
- })
-
- it('if tab is active and breakpoint is largeMedium', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- partitionNumber: 1,
- hoverState: true,
- breakpoint: 'largeMedium'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('NewSessionIcon').length, 0)
- })
-
- it('if breakpoint is medium', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- partitionNumber: 1,
- hoverState: false,
- breakpoint: 'medium'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('NewSessionIcon').length, 0)
+ it('if tab is not partitioned', function * () {
+ windowStore.state = defaultWindowStore
+ .setIn(['frames', index, 'partitionNumber'], false)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 0)
})
- it('if breakpoint is mediumSmall', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- partitionNumber: 1,
- hoverState: false,
- breakpoint: 'mediumSmall'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('NewSessionIcon').length, 0)
+ it('if tab is being hovered', function * () {
+ windowStore.state = defaultWindowStore
+ .setIn(['ui', 'tabs', 'hoverTabIndex'], index)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 0)
})
- it('if breakpoint is small', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- partitionNumber: 1,
- hoverState: true,
- breakpoint: 'small'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('NewSessionIcon').length, 0)
+ it('if for active tab if size is small', function * () {
+ windowStore.state = defaultWindowStore
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 0)
})
- it('if breakpoint is extraSmall', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- partitionNumber: 1,
- hoverState: true,
- breakpoint: 'extraSmall'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('NewSessionIcon').length, 0)
+ it('if tab is being intersected at 35% or less', function * () {
+ windowStore.state = defaultWindowStore
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at20)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 0)
})
- it('if breakpoint is the smallest', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- partitionNumber: 1,
- hoverState: true,
- breakpoint: 'smallest'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('NewSessionIcon').length, 0)
+ it('if partitionNumber is zero', function * () {
+ windowStore.state = defaultWindowStore
+ .setIn(['frames', index, 'partitionNumber'], 0)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 0)
})
})
})
diff --git a/test/unit/app/renderer/components/tabs/content/privateIconTest.js b/test/unit/app/renderer/components/tabs/content/privateIconTest.js
index c8c95cfeca4..88dc8d5dbaf 100644
--- a/test/unit/app/renderer/components/tabs/content/privateIconTest.js
+++ b/test/unit/app/renderer/components/tabs/content/privateIconTest.js
@@ -5,42 +5,45 @@
const mockery = require('mockery')
const {mount} = require('enzyme')
-const Immutable = require('immutable')
const assert = require('assert')
+const Immutable = require('immutable')
const fakeElectron = require('../../../../../lib/fakeElectron')
+const {intersection} = require('../../../../../../../app/renderer/components/styles/global')
require('../../../../../braveUnit')
const index = 0
const tabId = 1
const frameKey = 1
-const fakeAppStoreRenderer = {
- state: Immutable.fromJS({
- windows: [{
- windowId: 1,
- windowUUID: 'uuid'
- }],
- tabs: [{
- tabId: tabId,
- windowId: 1,
- windowUUID: 'uuid',
- url: 'https://brave.com'
- }]
- }),
- addChangeListener: () => {},
- removeChangeListener: () => {}
-}
+const fakeAppStoreRenderer = Immutable.fromJS({
+ windows: [{
+ windowId: 1,
+ windowUUID: 'uuid'
+ }],
+ tabs: [{
+ tabId: tabId,
+ windowId: 1,
+ windowUUID: 'uuid',
+ url: 'https://brave.com'
+ }],
+ tabsInternal: {
+ index: {
+ 1: 0
+ }
+ }
+})
const defaultWindowStore = Immutable.fromJS({
activeFrameKey: frameKey,
frames: [{
key: frameKey,
tabId: tabId,
- location: 'http://brave.com'
+ location: 'http://brave.com',
+ isPrivate: true
}],
tabs: [{
- index: index,
- key: frameKey
+ key: frameKey,
+ index: index
}],
framesInternal: {
index: {
@@ -52,13 +55,13 @@ const defaultWindowStore = Immutable.fromJS({
},
ui: {
tabs: {
- hoverTabIndex: index
+ tabHoverState: 1
}
}
})
describe('Tabs content - PrivateIcon', function () {
- let Tab, windowStore
+ let PrivateIcon, windowStore, appStore
before(function () {
mockery.enable({
@@ -67,14 +70,11 @@ describe('Tabs content - PrivateIcon', function () {
useCleanCache: true
})
mockery.registerMock('electron', fakeElectron)
- mockery.registerMock('../../../js/stores/appStoreRenderer', fakeAppStoreRenderer)
- mockery.registerMock('../../../../extensions/brave/img/tabs/loading.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/new_session.svg')
mockery.registerMock('../../../../extensions/brave/img/tabs/private.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/close_btn_hover.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/close_btn_normal.svg')
windowStore = require('../../../../../../../js/stores/windowStore')
- Tab = require('../../../../../../../app/renderer/components/tabs/tab')
+ appStore = require('../../../../../../../js/stores/appStoreRenderer')
+ PrivateIcon = require('../../../../../../../app/renderer/components/tabs/content/privateIcon')
+ appStore.state = fakeAppStoreRenderer
})
after(function () {
@@ -83,158 +83,55 @@ describe('Tabs content - PrivateIcon', function () {
})
describe('should show icon', function () {
- it('if current tab is private', function () {
- windowStore.state = defaultWindowStore.merge({
- activeFrameKey: 0,
- frames: [{
- isPrivate: true,
- breakpoint: 'default'
- }],
- ui: {
- tabs: {
- hoverTabIndex: null
- }
- }
- })
- const wrapper = mount()
- assert.equal(wrapper.find('PrivateIcon').length, 1)
- })
-
- it('if tab is not active and breakpoint is largeMedium', function () {
- windowStore.state = defaultWindowStore.merge({
- activeFrameKey: 0,
- frames: [{
- isPrivate: true,
- hoverState: false,
- breakpoint: 'largeMedium'
- }]
- })
- const wrapper = mount()
- assert.equal(wrapper.find('PrivateIcon').length, 1)
- })
-
- it('if mouse is not over tab and breakpoint is large', function () {
- windowStore.state = defaultWindowStore.merge({
- activeFrameKey: 0,
- frames: [{
- isPrivate: true,
- breakpoint: 'large'
- }],
- ui: {
- tabs: {
- hoverTabIndex: null
- }
- }
- })
- const wrapper = mount()
- assert.equal(wrapper.find('PrivateIcon').length, 1)
+ it('if tab is not hovered', function * () {
+ windowStore.state = defaultWindowStore
+ .mergeIn(['ui', 'tabs'], {
+ intersectionRatio: intersection.noIntersection,
+ tabHoverIndex: 1337
+ })
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 1)
})
- it('if mouse is not over tab and breakpoint is default', function () {
- windowStore.state = defaultWindowStore.merge({
- activeFrameKey: 0,
- frames: [{
- isPrivate: true,
- breakpoint: 'default'
- }],
- ui: {
- tabs: {
- hoverTabIndex: null
- }
- }
- })
- const wrapper = mount()
- assert.equal(wrapper.find('PrivateIcon').length, 1)
+ it('if tab is not active and size is small', function * () {
+ windowStore.state = defaultWindowStore
+ .mergeIn(['ui', 'tabs'], {
+ tabHoverIndex: 1337,
+ intersectionRatio: intersection.at45
+ })
+ .set('activeFrameKey', 1337)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 1)
})
})
describe('should not show icon', function () {
- it('if current tab is not private', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- isPrivate: false
- })
- const wrapper = mount()
- assert.equal(wrapper.find('PrivateIcon').length, 0)
- })
-
- it('if mouse is over tab and breakpoint is default', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- isPrivate: true,
- hoverState: true,
- breakpoint: 'default'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('PrivateIcon').length, 0)
- })
-
- it('if mouse is over tab and breakpoint is large', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- isPrivate: true,
- hoverState: true,
- breakpoint: 'large'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('PrivateIcon').length, 0)
- })
-
- it('if tab is active and breakpoint is largeMedium', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- isPrivate: true,
- hoverState: true,
- breakpoint: 'largeMedium'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('PrivateIcon').length, 0)
- })
-
- it('if breakpoint is medium', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- isPrivate: true,
- hoverState: false,
- breakpoint: 'medium'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('PrivateIcon').length, 0)
- })
-
- it('if breakpoint is mediumSmall', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- isPrivate: true,
- hoverState: false,
- breakpoint: 'mediumSmall'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('PrivateIcon').length, 0)
+ it('if tab is not private', function * () {
+ windowStore.state = defaultWindowStore
+ .setIn(['frames', index, 'isPrivate'], false)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 0)
})
- it('if breakpoint is small', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- isPrivate: true,
- hoverState: true,
- breakpoint: 'small'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('PrivateIcon').length, 0)
+ it('if tab is being hovered', function * () {
+ windowStore.state = defaultWindowStore
+ .setIn(['ui', 'tabs', 'hoverTabIndex'], index)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 0)
})
- it('if breakpoint is extraSmall', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- isPrivate: true,
- hoverState: true,
- breakpoint: 'extraSmall'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('PrivateIcon').length, 0)
+ it('if for active tab if size is small', function * () {
+ windowStore.state = defaultWindowStore
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 0)
})
- it('if breakpoint is the smallest', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- isPrivate: true,
- hoverState: true,
- breakpoint: 'smallest'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('PrivateIcon').length, 0)
+ it('if tab is being intersected at 35% or less', function * () {
+ windowStore.state = defaultWindowStore
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at20)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabIcon').length, 0)
})
})
})
diff --git a/test/unit/app/renderer/components/tabs/content/tabTitleTest.js b/test/unit/app/renderer/components/tabs/content/tabTitleTest.js
index f0924afab5c..c144e43fbab 100644
--- a/test/unit/app/renderer/components/tabs/content/tabTitleTest.js
+++ b/test/unit/app/renderer/components/tabs/content/tabTitleTest.js
@@ -1,49 +1,48 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/* global describe, before, it, after */
+/* global describe, before, after, it */
const mockery = require('mockery')
const {mount} = require('enzyme')
const assert = require('assert')
const Immutable = require('immutable')
const fakeElectron = require('../../../../../lib/fakeElectron')
+const {intersection} = require('../../../../../../../app/renderer/components/styles/global')
require('../../../../../braveUnit')
-const url1 = 'https://brave.com'
-const pageTitle1 = 'Brave Software'
+const index = 0
const tabId = 1
const frameKey = 1
-const invalidFrameKey = 71
-const fakeAppStoreRenderer = {
- state: Immutable.fromJS({
- windows: [{
- windowId: 1,
- windowUUID: 'uuid'
- }],
- tabs: [{
- tabId: tabId,
- windowId: 1,
- windowUUID: 'uuid',
- url: url1
- }]
- }),
- addChangeListener: () => {},
- removeChangeListener: () => {}
-}
+const fakeAppStoreRenderer = Immutable.fromJS({
+ windows: [{
+ windowId: 1,
+ windowUUID: 'uuid'
+ }],
+ tabs: [{
+ tabId: tabId,
+ windowId: 1,
+ windowUUID: 'uuid',
+ url: 'https://brave.com'
+ }],
+ tabsInternal: {
+ index: {
+ 1: 0
+ }
+ }
+})
const defaultWindowStore = Immutable.fromJS({
activeFrameKey: frameKey,
frames: [{
key: frameKey,
tabId: tabId,
- location: url1,
- title: pageTitle1
+ location: 'http://brave.com'
}],
tabs: [{
- key: frameKey
+ key: frameKey,
+ index: index
}],
framesInternal: {
index: {
@@ -52,12 +51,16 @@ const defaultWindowStore = Immutable.fromJS({
tabIndex: {
1: 0
}
+ },
+ ui: {
+ tabs: {
+ tabHoverState: 1
+ }
}
})
-describe('Tabs content - Title', function () {
- let Tab, windowStore
-
+describe('Tabs content - TabTitle', function () {
+ let TabTitle, windowStore, appStore
before(function () {
mockery.enable({
warnOnReplace: false,
@@ -65,14 +68,12 @@ describe('Tabs content - Title', function () {
useCleanCache: true
})
mockery.registerMock('electron', fakeElectron)
- mockery.registerMock('../../../js/stores/appStoreRenderer', fakeAppStoreRenderer)
- mockery.registerMock('../../../../extensions/brave/img/tabs/loading.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/new_session.svg')
+ mockery.registerMock('../../../../js/l10n', { translation: () => 'translated' })
mockery.registerMock('../../../../extensions/brave/img/tabs/private.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/close_btn_hover.svg')
- mockery.registerMock('../../../../extensions/brave/img/tabs/close_btn_normal.svg')
windowStore = require('../../../../../../../js/stores/windowStore')
- Tab = require('../../../../../../../app/renderer/components/tabs/tab')
+ appStore = require('../../../../../../../js/stores/appStoreRenderer')
+ TabTitle = require('../../../../../../../app/renderer/components/tabs/content/tabTitle')
+ appStore.state = fakeAppStoreRenderer
})
after(function () {
@@ -80,118 +81,86 @@ describe('Tabs content - Title', function () {
mockery.disable()
})
- describe('should show text', function () {
- it('if page has a title', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- location: url1,
- title: pageTitle1
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabTitle div').text(), pageTitle1)
- })
- it('if breakpoint is default', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- location: url1,
- title: pageTitle1,
- breakpoint: 'default'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabTitle div').text(), pageTitle1)
- })
- it('if breakpoint is large', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- location: url1,
- title: pageTitle1,
- breakpoint: 'large'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabTitle div').text(), pageTitle1)
+ describe('should show icon', function () {
+ it('if is not intersected at 35% of tab size', function * () {
+ windowStore.state = defaultWindowStore
+ .set('activeFrameKey', 1337)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at60)
+ .mergeIn(['frames', index], {
+ isPrivate: false,
+ partitionNumber: false
+ })
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabTitle').props().showTabTitle, true)
})
- it('if breakpoint is medium', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- location: url1,
- title: pageTitle1,
- breakpoint: 'medium'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabTitle div').text(), pageTitle1)
+
+ it('if not active and intersected at 45% of tab size with no private icon visible', function * () {
+ windowStore.state = defaultWindowStore
+ .set('activeFrameKey', 1337)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ .setIn(['frames', index, 'isPrivate'], false)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabTitle').props().showTabTitle, true)
})
- it('if breakpoint is mediumSmall and tab is not active', function () {
- windowStore.state = defaultWindowStore.merge({
- activeFrameKey: 0,
- frames: [{
- location: url1,
- title: pageTitle1,
- breakpoint: 'mediumSmall'
- }]
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabTitle div').text(), pageTitle1)
+
+ it('if not active and intersected at 45% of tab size with no partition icon visible', function * () {
+ windowStore.state = defaultWindowStore
+ .set('activeFrameKey', 1337)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ .setIn(['frames', index, 'partitionNumber'], null)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabTitle').props().showTabTitle, true)
})
- it('if breakpoint is small and tab is not active', function () {
- windowStore.state = defaultWindowStore.merge({
- activeFrameKey: 0,
- frames: [{
- location: url1,
- title: pageTitle1,
- breakpoint: 'small'
- }]
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabTitle div').text(), pageTitle1)
+
+ it('if is intersected at 45% of tab size and is about:newtab', function * () {
+ windowStore.state = defaultWindowStore
+ .set('activeFrameKey', 1337)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ .setIn(['frames', index, 'location'], 'about:newtab')
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabTitle').props().showTabTitle, true)
})
- it('passing in a frame key which does not exist does not fail', function () {
+
+ it('if is intersected at 45% of tab size and is not active', function * () {
windowStore.state = defaultWindowStore
- const wrapper = mount()
- assert.equal(wrapper.find('TabTitle div').text(), '')
+ .set('activeFrameKey', 1337)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabTitle').props().showTabTitle, true)
})
})
- describe('should not show text', function () {
- it('if tab is pinned', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- location: url1,
- title: pageTitle1,
- pinnedLocation: true
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabTitle').length, 0)
- })
- it('if breakpoint is mediumSmall and tab is active', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- location: url1,
- title: pageTitle1,
- breakpoint: 'mediumSmall'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabTitle').length, 0)
+ describe('should not show icon', function () {
+ it('if is intersected at 35% of tab size', function * () {
+ windowStore.state = defaultWindowStore
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at40)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabTitle').props().showTabTitle, false)
})
- it('if breakpoint is small and tab is active', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- location: url1,
- title: pageTitle1,
- breakpoint: 'small'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabTitle').length, 0)
+
+ it('if active and intersected at 45% of tab size', function * () {
+ windowStore.state = defaultWindowStore
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabTitle').props().showTabTitle, false)
})
- it('if breakpoint is extraSmall', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- location: url1,
- title: pageTitle1,
- breakpoint: 'extraSmall'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabTitle').length, 0)
+
+ it('if not active and intersected at 45% of tab size with partition icon visible', function * () {
+ windowStore.state = defaultWindowStore
+ .set('activeFrameKey', 1337)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ .setIn(['frames', index, 'partitionNumber'], 1)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabTitle').props().showTabTitle, false)
})
- it('if breakpoint is the smallest', function () {
- windowStore.state = defaultWindowStore.mergeIn(['frames', 0], {
- location: url1,
- title: pageTitle1,
- breakpoint: 'smallest'
- })
- const wrapper = mount()
- assert.equal(wrapper.find('TabTitle').length, 0)
+
+ it('if not active and intersected at 45% of tab size with private icon visible', function * () {
+ windowStore.state = defaultWindowStore
+ .set('activeFrameKey', 1337)
+ .setIn(['ui', 'tabs', 'intersectionRatio'], intersection.at45)
+ .setIn(['frames', index, 'isPrivate'], true)
+ const wrapper = mount()
+ assert.equal(wrapper.find('TabTitle').props().showTabTitle, false)
})
})
})
diff --git a/test/unit/js/stores/windowStoreTest.js b/test/unit/js/stores/windowStoreTest.js
index 6f8e5a294e1..7538230b7e5 100644
--- a/test/unit/js/stores/windowStoreTest.js
+++ b/test/unit/js/stores/windowStoreTest.js
@@ -169,7 +169,6 @@ describe('Window store unit tests', function () {
},
tabId: 8,
zoomLevel: 0,
- breakpoint: 'default',
index: 1,
partitionNumber: 0,
history: [],
diff --git a/test/unit/state/frameStateUtilTest.js b/test/unit/state/frameStateUtilTest.js
index 4752268dd8c..902e13a85fe 100644
--- a/test/unit/state/frameStateUtilTest.js
+++ b/test/unit/state/frameStateUtilTest.js
@@ -411,4 +411,37 @@ describe('frameStateUtil', function () {
assert.equal(result, 1)
})
})
+
+ describe('frameLocationMatch', function () {
+ before(function () {
+ this.frameKey = 1
+ this.location = 'nespresso.com'
+ this.state = defaultWindowStore.mergeIn(['frames', 0], {
+ location: this.location,
+ frameKey: this.frameKey
+ })
+ this.frame = this.state.getIn(['frames', 0])
+ })
+
+ it('returns false if frame is empty', function () {
+ const result = frameStateUtil.frameLocationMatch(null, this.location)
+ assert.equal(result, false)
+ })
+ it('returns false if frame is not an Immutable map', function () {
+ const result = frameStateUtil.frameLocationMatch(this.frame.toJS(), this.location)
+ assert.equal(result, false)
+ })
+ it('returns false if location is empty', function () {
+ const result = frameStateUtil.frameLocationMatch(this.frame, '')
+ assert.equal(result, false)
+ })
+ it('returns false if location is a partial match', function () {
+ const result = frameStateUtil.frameLocationMatch(this.frame, 'nespresso.co.uk')
+ assert.equal(result, false)
+ })
+ it('returns true if location match', function () {
+ const result = frameStateUtil.frameLocationMatch(this.frame, this.location)
+ assert.equal(result, true)
+ })
+ })
})