diff --git a/packages/desktop-gui/cypress/integration/settings_spec.js b/packages/desktop-gui/cypress/integration/settings_spec.js index f3755707aadd..24be6dd8901e 100644 --- a/packages/desktop-gui/cypress/integration/settings_spec.js +++ b/packages/desktop-gui/cypress/integration/settings_spec.js @@ -1,4 +1,5 @@ const { _ } = Cypress +const { each, flow, get, isString, join, map, merge, set, sortBy, toPairs } = require('lodash/fp') describe('Settings', () => { beforeEach(function () { @@ -181,6 +182,82 @@ describe('Settings', () => { expect(this.ipc.externalOpen).to.be.calledWith('https://on.cypress.io/guides/configuration') }) }) + + it('displays null when env settings are empty or not defined', function () { + this.ipc.openProject.resolves(setConfigEnv(this.config, undefined)) + this.ipc.onConfigChanged.yield() + + cy.contains('.line', 'env:null').then(() => { + this.ipc.openProject.resolves(this.config) + this.ipc.onConfigChanged.yield() + + cy.contains('.line', 'env:fileServerFolder') + .then(() => { + this.ipc.openProject.resolves(setConfigEnv(this.config, null)) + this.ipc.onConfigChanged.yield() + cy.contains('.line', 'env:null').then(() => { + this.ipc.openProject.resolves(this.config) + this.ipc.onConfigChanged.yield() + + cy.contains('.line', 'env:fileServerFolder') + .then(() => { + this.ipc.openProject.resolves(setConfigEnv(this.config, {})) + this.ipc.onConfigChanged.yield() + cy.contains('.line', 'env:null') + }) + }) + }) + }) + }) + + it('displays env settings', () => { + cy.get('@config').then(({ resolved }) => { + const getEnvKeys = flow([ + get('env'), + toPairs, + map(([key]) => key), + sortBy(get('')), + ]) + + const assertKeyExists = each((key) => cy.contains('.line', key)) + const assertKeyValuesExists = flow([ + map((key) => { + return flow([ + get(['env', key, 'value']), + (v) => { + if (isString(v)) { + return `"${v}"` + } + + return v + }, + ])(resolved) + }), + each((v) => { + cy.contains('.key-value-pair-value', v) + }), + ]) + + const assertFromTooltipsExist = flow([ + map((key) => { + return [key, + flow([ + get(['env', key, 'from']), + (from) => `.${from}`, + ])(resolved)] + }), + each(([key, fromTooltipClassName]) => { + cy.contains(key).parents('.line').first().find(fromTooltipClassName) + }), + ]) + + cy.contains('.line', 'env').contains(flow([getEnvKeys, join(', ')])(resolved)) + cy.contains('.line', 'env').click() + flow([getEnvKeys, assertKeyExists])(resolved) + flow([getEnvKeys, assertKeyValuesExists])(resolved) + flow([getEnvKeys, assertFromTooltipsExist])(resolved) + }) + }) }) describe('when project id panel is opened', () => { @@ -519,3 +596,11 @@ describe('Settings', () => { }) }) }) + +// -- +function setConfigEnv (config, v) { + return flow([ + merge(config), + set('resolved.env', v), + ])({}) +} diff --git a/packages/desktop-gui/src/settings/configuration.jsx b/packages/desktop-gui/src/settings/configuration.jsx index ef05d286675c..4f1cd17d1930 100644 --- a/packages/desktop-gui/src/settings/configuration.jsx +++ b/packages/desktop-gui/src/settings/configuration.jsx @@ -1,38 +1,58 @@ import _ from 'lodash' +import { defaultTo, get, flow, isEmpty, join, map, reduce, take, toPairs, toPath } from 'lodash/fp' import cn from 'classnames' import { observer } from 'mobx-react' import React from 'react' import Tooltip from '@cypress/react-tooltip' import { ObjectInspector, ObjectName } from 'react-inspector' - import { configFileFormatted } from '../lib/config-file-formatted' import ipc from '../lib/ipc' -const formatData = (data) => { - if (Array.isArray(data)) { - return _.map(data, (v) => { - if (_.isObject(v) && (v.name || v.displayName)) { - return _.defaultTo(v.displayName, v.name) - } - - return String(v) - }).join(', ') +const joinWithCommas = join(', ') +const objToString = (v) => flow([ + defaultTo(v.name), + defaultTo(_.isObject(v) ? joinWithCommas(Object.keys(v)) : undefined), + defaultTo(String(v)), +])(v.displayName) + +const formatValue = (value) => { + if (Array.isArray(value)) { + return flow([ + map(objToString), + joinWithCommas, + ])(value) } - if (_.isObject(data)) { - return _.defaultTo(_.defaultTo(data.displayName, data.name), String(Object.keys(data).join(', '))) + if (_.isObject(value)) { + return objToString(value) } const excludedFromQuotations = ['null', 'undefined'] - if (_.isString(data) && !excludedFromQuotations.includes(data)) { - return `"${data}"` + if (_.isString(value) && !excludedFromQuotations.includes(value)) { + return `"${value}"` } - return String(data) + return String(value) } + +const normalizeWithoutMeta = flow([ + defaultTo({}), + toPairs, + reduce((acc, [key, value]) => _.merge({}, acc, { + [key]: value ? value.value : {}, + }), {}), + (v) => { + if (isEmpty(v)) { + return null + } + + return v + }, +]) + const ObjectLabel = ({ name, data, expanded, from, isNonenumerable }) => { - const formattedData = formatData(data) + const formattedData = formatValue(data) return ( @@ -40,11 +60,18 @@ const ObjectLabel = ({ name, data, expanded, from, isNonenumerable }) => { : {!expanded && ( <> - + {from && ( + + + {formattedData} + + + )} + {!from && ( {formattedData} - + )} )} {expanded && Array.isArray(data) && ( @@ -58,25 +85,36 @@ ObjectLabel.defaultProps = { data: 'undefined', } -const createComputeFromValue = (obj) => { - return (name, path) => { - const pathParts = path.split('.') - const pathDepth = pathParts.length +const computeFromValue = (obj, name, path) => { + const normalizedPath = path.replace('$.', '').replace(name, `['${name}']`) + const getValueForPath = flow([ + toPath, + _.partialRight(get, obj), + ]) + + let value = getValueForPath(normalizedPath) + + if (!value) { + const onlyFirstKeyInPath = flow([toPath, take(1)]) - const rootKey = pathDepth <= 2 ? name : pathParts[1] + value = getValueForPath(onlyFirstKeyInPath(normalizedPath)) + } - return obj[rootKey] ? obj[rootKey].from : undefined + if (!value) { + return undefined } + + return value.from ? value.from : undefined } const ConfigDisplay = ({ data: obj }) => { - const computeFromValue = createComputeFromValue(obj) + const getFromValue = _.partial(computeFromValue, obj) const renderNode = ({ depth, name, data, isNonenumerable, expanded, path }) => { if (depth === 0) { return null } - const from = computeFromValue(name, path) + const from = getFromValue(name, path) return ( { ) } - const data = _.reduce(obj, (acc, value, key) => Object.assign(acc, { - [key]: value.value, - }), {}) + const data = normalizeWithoutMeta(obj) + + data.env = normalizeWithoutMeta(obj.env) return (