<commit_before>var fs = require('fs')
var path = require('path')
var mkdirp = require('mkdirp')
var helpers = require('broccoli-kitchen-sink-helpers')
var Writer = require('broccoli-writer')
var jsStringEscape = require('js-string-escape')
var crypto = require('crypto')
var quickTemp = require('quick-temp')
var glob = require('glob')
var htmlparser = require("htmlparser2")
var domSerializer = require("dom-serializer")

module.exports = Concat

Concat.prototype = Object.create(Writer.prototype)
Concat.prototype.constructor = Concat
function Concat(inputTree, options) {
  if (!(this instanceof Concat)) return new Concat(inputTree, options)
  this.inputTree = inputTree
  for (var key in options) {
    if (options.hasOwnProperty(key)) {
      this[key] = options[key]
    }
  }

  this.cache = {}
  this.cachedConcatenatedOutputHash = null
}

Concat.prototype.cleanup = function(){
  Writer.prototype.cleanup.call(this)
  quickTemp.remove(this, 'tmpCacheDir')
}

Concat.prototype.write = function (readTree, destDir) {
  var self = this
    return readTree(this.inputTree).then(function (srcDir) {
      helpers.copyRecursivelySync(srcDir, destDir)

      function rewriteFile(filePath, stat) {
        var fileContents = fs.readFileSync(srcDir + '/' + filePath, { encoding: 'utf8' })
        var rewrittenFileContents = rewriteString(fileContents, srcDir)
        fs.writeFileSync(path.join(destDir, filePath), rewrittenFileContents)
      }

      var inputHtml = helpers.multiGlob(self.htmlFiles, {cwd: srcDir})
        for (i = 0; i < inputHtml.length; i++) {
          var stat = getStat(srcDir + '/' + inputHtml[i]);
          if (stat && stat.isFile()) {
            rewriteFile(inputHtml[i], stat)
          }
        }
    })
}

function rewriteString(s, rootDir) {
  var replacements = []
  var inBlock = false;
  var inScriptTag = false;
  var currentScriptTag;
  var leadingWhitespace = {};
  var parser = new htmlparser.Parser({
    oncomment: function(text) {
      if (text.match(/^\s*build:(js|css)\s*(.+)\s*$/)) {
        inBlock = true
      } else if (text.match(/^\s*endbuild\s*$/)) {
        inBlock = false
        inScriptTag = false
      }
    },
    onopentag: function(name, attributes) {
      if (inBlock && name === "script" && attributes.src) {
        inScriptTag = true
        currentScriptTag = {
          attrs: attributes,
          startIndex: parser.startIndex,
          whitespace: ''
        }

        if (leadingWhitespace.endIndex === parser.startIndex - 1) {
          currentScriptTag.whitespace = leadingWhitespace.str
        }
      }
    },
    onclosetag: function(name) {
      if (inScriptTag && name === "script") {
        attributes = currentScriptTag.attrs
        inScriptTag = false
        var files = helpers.multiGlob([attributes.src], {cwd: rootDir})
        var newTags = files.map(function(file) {
          attributes.src = file
          scriptDom = {
            type: 'tag',
            name: 'script',
            attribs: attributes,
            children: []
          }
          return domSerializer(scriptDom)
        }).join(currentScriptTag.whitespace)
        replacements.push({startIndex: currentScriptTag.startIndex, endIndex: parser.endIndex, str: newTags})
      }
    },
    ontext: function(text) {
      if (text.match(/^\s+$/)) {
        var lastNewline = text.lastIndexOf(/\r|\n/)
        if (lastNewline === -1) {
          lastNewline = 0
        }
        leadingWhitespace = {endIndex: parser.endIndex, str: text.slice(lastNewline )}
      }
    }
  })
  parser.write(s);
  parser.end();

  lastIndex = 0
  newHtmlChunks = []
  replacements.forEach(function(replacement) {
    newHtmlChunks.push(s.slice(lastIndex, replacement.startIndex))
    newHtmlChunks.push(replacement.str)
    lastIndex = replacement.endIndex + 1
  })
  newHtmlChunks.push(s.slice(lastIndex))
  return newHtmlChunks.join('')
}

function getStat(path) {
  try {
    return fs.statSync(path);
  } catch(error) {
    if (error.code !== 'ENOENT') {
      throw error;
    }
    return null;
  }
}
<commit_msg>Rename export object<commit_after>var fs = require('fs')
var path = require('path')
var mkdirp = require('mkdirp')
var helpers = require('broccoli-kitchen-sink-helpers')
var Writer = require('broccoli-writer')
var jsStringEscape = require('js-string-escape')
var crypto = require('crypto')
var quickTemp = require('quick-temp')
var glob = require('glob')
var htmlparser = require("htmlparser2")
var domSerializer = require("dom-serializer")

module.exports = Pipeline

Pipeline.prototype = Object.create(Writer.prototype)
Pipeline.prototype.constructor = Pipeline
function Pipeline(inputTree, options) {
  if (!(this instanceof Pipeline)) return new Pipeline(inputTree, options)
  this.inputTree = inputTree
  for (var key in options) {
    if (options.hasOwnProperty(key)) {
      this[key] = options[key]
    }
  }

  this.cache = {}
  this.cachedConcatenatedOutputHash = null
}

Pipeline.prototype.cleanup = function(){
  Writer.prototype.cleanup.call(this)
  quickTemp.remove(this, 'tmpCacheDir')
}

Pipeline.prototype.write = function (readTree, destDir) {
  var self = this
    return readTree(this.inputTree).then(function (srcDir) {
      helpers.copyRecursivelySync(srcDir, destDir)

      function rewriteFile(filePath, stat) {
        var fileContents = fs.readFileSync(srcDir + '/' + filePath, { encoding: 'utf8' })
        var rewrittenFileContents = rewriteString(fileContents, srcDir)
        fs.writeFileSync(path.join(destDir, filePath), rewrittenFileContents)
      }

      var inputHtml = helpers.multiGlob(self.htmlFiles, {cwd: srcDir})
        for (i = 0; i < inputHtml.length; i++) {
          var stat = getStat(srcDir + '/' + inputHtml[i]);
          if (stat && stat.isFile()) {
            rewriteFile(inputHtml[i], stat)
          }
        }
    })
}

function rewriteString(s, rootDir) {
  var replacements = []
  var inBlock = false;
  var inScriptTag = false;
  var currentScriptTag;
  var leadingWhitespace = {};
  var parser = new htmlparser.Parser({
    oncomment: function(text) {
      if (text.match(/^\s*build:(js|css)\s*(.+)\s*$/)) {
        inBlock = true
      } else if (text.match(/^\s*endbuild\s*$/)) {
        inBlock = false
        inScriptTag = false
      }
    },
    onopentag: function(name, attributes) {
      if (inBlock && name === "script" && attributes.src) {
        inScriptTag = true
        currentScriptTag = {
          attrs: attributes,
          startIndex: parser.startIndex,
          whitespace: ''
        }

        if (leadingWhitespace.endIndex === parser.startIndex - 1) {
          currentScriptTag.whitespace = leadingWhitespace.str
        }
      }
    },
    onclosetag: function(name) {
      if (inScriptTag && name === "script") {
        attributes = currentScriptTag.attrs
        inScriptTag = false
        var files = helpers.multiGlob([attributes.src], {cwd: rootDir})
        var newTags = files.map(function(file) {
          attributes.src = file
          scriptDom = {
            type: 'tag',
            name: 'script',
            attribs: attributes,
            children: []
          }
          return domSerializer(scriptDom)
        }).join(currentScriptTag.whitespace)
        replacements.push({startIndex: currentScriptTag.startIndex, endIndex: parser.endIndex, str: newTags})
      }
    },
    ontext: function(text) {
      if (text.match(/^\s+$/)) {
        var lastNewline = text.lastIndexOf(/\r|\n/)
        if (lastNewline === -1) {
          lastNewline = 0
        }
        leadingWhitespace = {endIndex: parser.endIndex, str: text.slice(lastNewline )}
      }
    }
  })
  parser.write(s);
  parser.end();

  lastIndex = 0
  newHtmlChunks = []
  replacements.forEach(function(replacement) {
    newHtmlChunks.push(s.slice(lastIndex, replacement.startIndex))
    newHtmlChunks.push(replacement.str)
    lastIndex = replacement.endIndex + 1
  })
  newHtmlChunks.push(s.slice(lastIndex))
  return newHtmlChunks.join('')
}

function getStat(path) {
  try {
    return fs.statSync(path);
  } catch(error) {
    if (error.code !== 'ENOENT') {
      throw error;
    }
    return null;
  }
}
<|endoftext|>


<commit_before>'use strict'

var pkg = require('./package.json')
var Promise = require('bluebird')
var fs = Promise.promisifyAll(require('fs'))
var request = require('request')
var url = require('url')
var path = require('path')
var os = require('os')
var system = (process.platform === 'linux') ? require('./src/system.js') : require('./src/mockSystem.js')

var Context = require('./src/context.js')
var Callback = require('./src/callback.js')

const VERSION = pkg.version
const DEFAULT_COLLECTOR_URL = 'https://metrics-api.iopipe.com'

function _make_generateLog(metrics, func, start_time, config, context) {
  var pre_stat_promise = system.readstat('self')

  return function generateLog(err, callback) {
    Promise.join(
        pre_stat_promise,
        system.readstat('self'),
        system.readstatus('self'),
        system.readbootid()
      ).spread((
        pre_proc_self_stat,
        proc_self_stat,
        proc_self_status,
        boot_id
      ) => {
        var runtime_env = {
          agent: {
            runtime: 'nodejs',
            version: VERSION
          },
          host: {
            vm_id: boot_id
          },
          os: {
            hostname: os.hostname(),
            uptime: os.uptime(),
            totalmem: os.totalmem(),
            freemem: os.freemem(),
            usedmem: os.totalmem() - os.freemem(),
            cpus: os.cpus(),
            arch: os.arch(),
            linux: {
              pid: {
                self: {
                  stat_start: pre_proc_self_stat,
                  stat: proc_self_stat,
                  status: proc_self_status
                }
              }
            }
          },
          nodejs: {
            title: process.title,
            version: process.version,
            modulesloadList: process.modulesloadList,
            versions: process.versions,
            arch: process.arch,
            platform: process.platform,
            argv: process.argv,
            execArgv: process.execArgv,
            pid: process.pid,
            features: process.features,
            execPath: process.execPath,
            debugPort: process.debugPort,
            _maxListeners: process._maxListeners,
            config: process.config,
            maxTickDepth: process.maxTickDepth,
            // /* Circular ref */ mainModule: process.mainModule,
            release: process.release,
            uptime: process.uptime(),
            getuid: process.getuid(),
            getgid: process.getgid(),
            geteuid: process.geteuid(),
            getegid: process.getegid(),
            memoryUsage: process.memoryUsage(),
          }
        }

        var retainErr = {}
        if (err) {
          retainErr = ((err) => {
            return {
              name: err.name,
              message: err.message,
              stack: err.stack,
              lineNumber: err.lineNumber,
              columnNumber: err.columnNumber,
              fileName: err.fileName
            }
          })((typeof(err) === 'string') ? new Error(err) : err)
        }

        var time_sec_nanosec = process.hrtime(start_time)

        /*
         depreciated fields:
         - time_sec_nanosec (use duration; 11/23/2016)
         - time_sec (use duration; 11/23/2016)
        */
        var response_body = {
          environment: runtime_env,
          aws: {
            functionName: context.functionName,
            functionVersion: context.functionVersion,
            invokedFunctionArn: context.invokedFunctionArn,
            memoryLimitInMB: context.memoryLimitInMB,
            awsRequestId: context.awsRequestId,
            logGroupName: context.logGroupName,
            logStreamName: context.logStreamName
          },
          errors: retainErr,
          custom_metrics: metrics,
          time_sec_nanosec: time_sec_nanosec,
          time_sec: time_sec_nanosec[0],
          time_nanosec: time_sec_nanosec[1],
          duration: Math.ceil(time_sec_nanosec[0] * 1000000000.0 + time_sec_nanosec[1]),
          client_id: config.clientId
        }

        if (context.getRemainingTimeInMillis) {
          response_body['getRemainingTimeInMillis'] = context.getRemainingTimeInMillis()
        }

        if (config.debug) {
          console.log('IOPIPE-DEBUG: ', response_body)
        }

        if (!config.clientId) {
          callback()
          return
        }
        request(
          {
            url: config.url,
            method: 'POST',
            json: true,
            body: response_body
          },
          function(err) {
            // Throw uncaught errors from the wrapped function.
            if (err) {
              context.fail(err)
            }
            callback()
          }
        )
      }
    )
  }
}

function setConfig(configObject) {
  var baseurl = (configObject && configObject.url) ? configObject.url : DEFAULT_COLLECTOR_URL
  var eventURL = url.parse(baseurl)
  eventURL.pathname = path.join(eventURL.pathname, 'v0/event')
  eventURL.path = eventURL.search ? eventURL.pathname + eventURL.search : eventURL.pathname

  return {
    url: eventURL,
    clientId: configObject && configObject.clientId || process.env.IOPIPE_CLIENTID || '',
    debug: configObject && configObject.debug || process.env.IOPIPE_DEBUG || false
  }
}

module.exports = function(options) {
  var fn = function(func) {
    fn.metricsQueue = []

    const config = setConfig(options)

    return function() {
      let args = [].slice.call(arguments)

      var start_time = process.hrtime()
      var generateLog = _make_generateLog(fn.metricsQueue, func, start_time, config, args[1])

      /* Mangle arguments, wrapping callbacks. */
      args[1] = Context(generateLog, args[1])
      args[2] = Callback(generateLog, args[2])

      try {
        return func.apply(this, args)
      }
      catch (err) {
        generateLog(err, () => {})
        return undefined
      }
    }
  }

  fn.log = function(name, value) {
    var numberValue, stringValue
    if (typeof value === 'number') {
      numberValue = value
    } else {
      if(typeof value === 'object') {
        JSON.stringify(value)
      } else {
        stringValue = String(value)
      }
    }
    fn.metricsQueue.push({
      name: name,
      n: numberValue,
      s: stringValue
    })
  }

  return fn
}
<commit_msg>Remove an extra require, whitespace<commit_after>'use strict'

var pkg = require('./package.json')
var Promise = require('bluebird')
var request = require('request')
var url = require('url')
var path = require('path')
var os = require('os')

var system = (process.platform === 'linux') ? require('./src/system.js') : require('./src/mockSystem.js')
var Context = require('./src/context.js')
var Callback = require('./src/callback.js')

const VERSION = pkg.version
const DEFAULT_COLLECTOR_URL = 'https://metrics-api.iopipe.com'

function _make_generateLog(metrics, func, start_time, config, context) {
  var pre_stat_promise = system.readstat('self')

  return function generateLog(err, callback) {
    Promise.join(
        pre_stat_promise,
        system.readstat('self'),
        system.readstatus('self'),
        system.readbootid()
      ).spread((
        pre_proc_self_stat,
        proc_self_stat,
        proc_self_status,
        boot_id
      ) => {
        var runtime_env = {
          agent: {
            runtime: 'nodejs',
            version: VERSION
          },
          host: {
            vm_id: boot_id
          },
          os: {
            hostname: os.hostname(),
            uptime: os.uptime(),
            totalmem: os.totalmem(),
            freemem: os.freemem(),
            usedmem: os.totalmem() - os.freemem(),
            cpus: os.cpus(),
            arch: os.arch(),
            linux: {
              pid: {
                self: {
                  stat_start: pre_proc_self_stat,
                  stat: proc_self_stat,
                  status: proc_self_status
                }
              }
            }
          },
          nodejs: {
            title: process.title,
            version: process.version,
            modulesloadList: process.modulesloadList,
            versions: process.versions,
            arch: process.arch,
            platform: process.platform,
            argv: process.argv,
            execArgv: process.execArgv,
            pid: process.pid,
            features: process.features,
            execPath: process.execPath,
            debugPort: process.debugPort,
            _maxListeners: process._maxListeners,
            config: process.config,
            maxTickDepth: process.maxTickDepth,
            // /* Circular ref */ mainModule: process.mainModule,
            release: process.release,
            uptime: process.uptime(),
            getuid: process.getuid(),
            getgid: process.getgid(),
            geteuid: process.geteuid(),
            getegid: process.getegid(),
            memoryUsage: process.memoryUsage(),
          }
        }

        var retainErr = {}
        if (err) {
          retainErr = ((err) => {
            return {
              name: err.name,
              message: err.message,
              stack: err.stack,
              lineNumber: err.lineNumber,
              columnNumber: err.columnNumber,
              fileName: err.fileName
            }
          })((typeof(err) === 'string') ? new Error(err) : err)
        }

        var time_sec_nanosec = process.hrtime(start_time)

        /*
         depreciated fields:
         - time_sec_nanosec (use duration; 11/23/2016)
         - time_sec (use duration; 11/23/2016)
        */
        var response_body = {
          environment: runtime_env,
          aws: {
            functionName: context.functionName,
            functionVersion: context.functionVersion,
            invokedFunctionArn: context.invokedFunctionArn,
            memoryLimitInMB: context.memoryLimitInMB,
            awsRequestId: context.awsRequestId,
            logGroupName: context.logGroupName,
            logStreamName: context.logStreamName
          },
          errors: retainErr,
          custom_metrics: metrics,
          time_sec_nanosec: time_sec_nanosec,
          time_sec: time_sec_nanosec[0],
          time_nanosec: time_sec_nanosec[1],
          duration: Math.ceil(time_sec_nanosec[0] * 1000000000.0 + time_sec_nanosec[1]),
          client_id: config.clientId
        }

        if (context.getRemainingTimeInMillis) {
          response_body['getRemainingTimeInMillis'] = context.getRemainingTimeInMillis()
        }

        if (config.debug) {
          console.log('IOPIPE-DEBUG: ', response_body)
        }

        if (!config.clientId) {
          callback()
          return
        }
        request(
          {
            url: config.url,
            method: 'POST',
            json: true,
            body: response_body
          },
          function(err) {
            // Throw uncaught errors from the wrapped function.
            if (err) {
              context.fail(err)
            }
            callback()
          }
        )
      }
    )
  }
}

function setConfig(configObject) {
  var baseurl = (configObject && configObject.url) ? configObject.url : DEFAULT_COLLECTOR_URL
  var eventURL = url.parse(baseurl)
  eventURL.pathname = path.join(eventURL.pathname, 'v0/event')
  eventURL.path = eventURL.search ? eventURL.pathname + eventURL.search : eventURL.pathname

  return {
    url: eventURL,
    clientId: configObject && configObject.clientId || process.env.IOPIPE_CLIENTID || '',
    debug: configObject && configObject.debug || process.env.IOPIPE_DEBUG || false
  }
}

module.exports = function(options) {
  var fn = function(func) {
    fn.metricsQueue = []
    const config = setConfig(options)

    return function() {
      let args = [].slice.call(arguments)

      var start_time = process.hrtime()
      var generateLog = _make_generateLog(fn.metricsQueue, func, start_time, config, args[1])

      /* Mangle arguments, wrapping callbacks. */
      args[1] = Context(generateLog, args[1])
      args[2] = Callback(generateLog, args[2])

      try {
        return func.apply(this, args)
      }
      catch (err) {
        generateLog(err, () => {})
        return undefined
      }
    }
  }

  fn.log = function(name, value) {
    var numberValue, stringValue
    if (typeof value === 'number') {
      numberValue = value
    } else {
      if(typeof value === 'object') {
        JSON.stringify(value)
      } else {
        stringValue = String(value)
      }
    }
    fn.metricsQueue.push({
      name: name,
      n: numberValue,
      s: stringValue
    })
  }

  return fn
}
<|endoftext|>


<commit_before>/**
 * Tag Manager Settings Edit component tests.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import { provideSiteInfo, provideUserAuthentication } from '../../../../../../tests/js/utils';
import { render, waitFor, createTestRegistry } from '../../../../../../tests/js/test-utils';
import { AMP_MODE_PRIMARY, AMP_MODE_SECONDARY } from '../../../../googlesitekit/datastore/site/constants';
import { STORE_NAME as CORE_MODULES } from '../../../../googlesitekit/modules/datastore/constants';
import { withActive } from '../../../../googlesitekit/modules/datastore/__fixtures__';
import { STORE_NAME as CORE_FORMS } from '../../../../googlesitekit/datastore/forms/constants';
import { STORE_NAME, CONTEXT_WEB, CONTAINER_CREATE, FORM_SETUP, CONTEXT_AMP } from '../../datastore/constants';
import { buildAccountWithContainers } from '../../datastore/__factories__';
import SetupMain from './SetupMain';

describe( 'SetupMain', () => {
	const siteName = 'Test Site';

	let registry;
	let allContainers;

	beforeEach( () => {
		const args = {
			container: { usageContext: [ CONTEXT_WEB, CONTEXT_AMP ] },
			count: 5,
		};

		const { account, containers } = buildAccountWithContainers( args );
		const accountID = account.accountId; // eslint-disable-line sitekit/camelcase-acronyms

		registry = createTestRegistry();

		fetchMock.getOnce( /tagmanager\/data\/accounts/, { body: [ account ], status: 200 } );
		fetchMock.getOnce( /analytics\/data\/settings/, { body: {}, status: 200 } );

		provideSiteInfo( registry, { siteName } );
		provideUserAuthentication( registry );

		registry.dispatch( CORE_MODULES ).receiveGetModules( withActive( 'tagmanager' ) );

		registry.dispatch( STORE_NAME ).setSettings( {} );
		registry.dispatch( STORE_NAME ).receiveGetExistingTag( null );
		registry.dispatch( STORE_NAME ).setAccountID( accountID );
		registry.dispatch( STORE_NAME ).receiveGetContainers( containers, { accountID } );

		allContainers = containers;
	} );

	describe( 'new containers', () => {
		describe( 'with no AMP', () => {
			beforeEach( () => {
				registry.dispatch( STORE_NAME ).setContainerID( CONTAINER_CREATE );
				registry.dispatch( STORE_NAME ).setInternalContainerID( '' );
			} );

			it( 'should display a default container name when nothing is entered yet', async () => {
				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );
				expect( container.querySelector( '#containerName' ) ).toHaveValue( siteName );
			} );

			it( 'should use a domain name as a default value when siteName is empty', async () => {
				provideSiteInfo( registry, { siteName: '' } );
				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );
				expect( container.querySelector( '#containerName' ) ).toHaveValue( 'example.com' );
			} );

			it( 'should display an error when a non-unique container name is used', async () => {
				registry.dispatch( CORE_FORMS ).setValues( FORM_SETUP, { containerName: allContainers[ 0 ].name } );

				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );

				expect( container.querySelector( '#containerName' ) ).toHaveValue( allContainers[ 0 ].name );
				expect( container.querySelector( '.googlesitekit-tagmanager-containername > .mdc-text-field' ) ).toHaveClass( 'mdc-text-field--error' );
			} );
		} );

		describe( 'with primary AMP', () => {
			beforeEach( () => {
				provideSiteInfo( registry, {
					siteName,
					ampMode: AMP_MODE_PRIMARY,
				} );

				registry.dispatch( STORE_NAME ).setAMPContainerID( CONTAINER_CREATE );
				registry.dispatch( STORE_NAME ).setInternalAMPContainerID( '' );
			} );

			it( 'should display a default container name when nothing is entered yet', async () => {
				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );
				expect( container.querySelector( '#ampContainerName' ) ).toHaveValue( `${ siteName } AMP` );
			} );

			it( 'should use a domain name as a default value when siteName is empty', async () => {
				provideSiteInfo( registry, { siteName: '' } );
				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );
				expect( container.querySelector( '#ampContainerName' ) ).toHaveValue( 'example.com AMP' );
			} );

			it( 'should display an error when a non-unique container name is used', async () => {
				registry.dispatch( CORE_FORMS ).setValues( FORM_SETUP, { ampContainerName: allContainers[ 0 ].name } );

				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );

				expect( container.querySelector( '#ampContainerName' ) ).toHaveValue( allContainers[ 0 ].name );
				expect( container.querySelector( '.googlesitekit-tagmanager-containername > .mdc-text-field' ) ).toHaveClass( 'mdc-text-field--error' );
			} );
		} );

		describe( 'with secondary AMP', () => {
			beforeEach( () => {
				provideSiteInfo( registry, {
					siteName,
					ampMode: AMP_MODE_SECONDARY,
				} );

				registry.dispatch( STORE_NAME ).setContainerID( CONTAINER_CREATE );
				registry.dispatch( STORE_NAME ).setInternalContainerID( '' );

				registry.dispatch( STORE_NAME ).setAMPContainerID( CONTAINER_CREATE );
				registry.dispatch( STORE_NAME ).setInternalAMPContainerID( '' );
			} );

			it( 'should display default container names when nothing is entered yet', async () => {
				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );
				expect( container.querySelector( '#containerName' ) ).toHaveValue( siteName );
				expect( container.querySelector( '#ampContainerName' ) ).toHaveValue( `${ siteName } AMP` );
			} );

			it( 'should use domain name as default values when siteName is empty', async () => {
				provideSiteInfo( registry, { siteName: '' } );
				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );
				expect( container.querySelector( '#containerName' ) ).toHaveValue( 'example.com' );
				expect( container.querySelector( '#ampContainerName' ) ).toHaveValue( 'example.com AMP' );
			} );

			it( 'should display errors when non-unique container names are used', async () => {
				registry.dispatch( CORE_FORMS ).setValues( FORM_SETUP, {
					containerName: allContainers[ 0 ].name,
					ampContainerName: allContainers[ 1 ].name,
				} );

				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );

				expect( container.querySelector( '#containerName' ) ).toHaveValue( allContainers[ 0 ].name );
				expect( container.querySelector( '.googlesitekit-tagmanager-containerName > .mdc-text-field' ) ).toHaveClass( 'mdc-text-field--error' );

				expect( container.querySelector( '#ampContainerName' ) ).toHaveValue( allContainers[ 1 ].name );
				expect( container.querySelector( '.googlesitekit-tagmanager-ampContainerName > .mdc-text-field' ) ).toHaveClass( 'mdc-text-field--error' );
			} );
		} );
	} );
} );
<commit_msg>Update assets/js/modules/tagmanager/components/setup/SetupMain.test.js<commit_after>/**
 * Tag Manager Setup Main component tests.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Internal dependencies
 */
import { provideSiteInfo, provideUserAuthentication } from '../../../../../../tests/js/utils';
import { render, waitFor, createTestRegistry } from '../../../../../../tests/js/test-utils';
import { AMP_MODE_PRIMARY, AMP_MODE_SECONDARY } from '../../../../googlesitekit/datastore/site/constants';
import { STORE_NAME as CORE_MODULES } from '../../../../googlesitekit/modules/datastore/constants';
import { withActive } from '../../../../googlesitekit/modules/datastore/__fixtures__';
import { STORE_NAME as CORE_FORMS } from '../../../../googlesitekit/datastore/forms/constants';
import { STORE_NAME, CONTEXT_WEB, CONTAINER_CREATE, FORM_SETUP, CONTEXT_AMP } from '../../datastore/constants';
import { buildAccountWithContainers } from '../../datastore/__factories__';
import SetupMain from './SetupMain';

describe( 'SetupMain', () => {
	const siteName = 'Test Site';

	let registry;
	let allContainers;

	beforeEach( () => {
		const args = {
			container: { usageContext: [ CONTEXT_WEB, CONTEXT_AMP ] },
			count: 5,
		};

		const { account, containers } = buildAccountWithContainers( args );
		const accountID = account.accountId; // eslint-disable-line sitekit/camelcase-acronyms

		registry = createTestRegistry();

		fetchMock.getOnce( /tagmanager\/data\/accounts/, { body: [ account ], status: 200 } );
		fetchMock.getOnce( /analytics\/data\/settings/, { body: {}, status: 200 } );

		provideSiteInfo( registry, { siteName } );
		provideUserAuthentication( registry );

		registry.dispatch( CORE_MODULES ).receiveGetModules( withActive( 'tagmanager' ) );

		registry.dispatch( STORE_NAME ).setSettings( {} );
		registry.dispatch( STORE_NAME ).receiveGetExistingTag( null );
		registry.dispatch( STORE_NAME ).setAccountID( accountID );
		registry.dispatch( STORE_NAME ).receiveGetContainers( containers, { accountID } );

		allContainers = containers;
	} );

	describe( 'new containers', () => {
		describe( 'with no AMP', () => {
			beforeEach( () => {
				registry.dispatch( STORE_NAME ).setContainerID( CONTAINER_CREATE );
				registry.dispatch( STORE_NAME ).setInternalContainerID( '' );
			} );

			it( 'should display a default container name when nothing is entered yet', async () => {
				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );
				expect( container.querySelector( '#containerName' ) ).toHaveValue( siteName );
			} );

			it( 'should use a domain name as a default value when siteName is empty', async () => {
				provideSiteInfo( registry, { siteName: '' } );
				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );
				expect( container.querySelector( '#containerName' ) ).toHaveValue( 'example.com' );
			} );

			it( 'should display an error when a non-unique container name is used', async () => {
				registry.dispatch( CORE_FORMS ).setValues( FORM_SETUP, { containerName: allContainers[ 0 ].name } );

				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );

				expect( container.querySelector( '#containerName' ) ).toHaveValue( allContainers[ 0 ].name );
				expect( container.querySelector( '.googlesitekit-tagmanager-containername > .mdc-text-field' ) ).toHaveClass( 'mdc-text-field--error' );
			} );
		} );

		describe( 'with primary AMP', () => {
			beforeEach( () => {
				provideSiteInfo( registry, {
					siteName,
					ampMode: AMP_MODE_PRIMARY,
				} );

				registry.dispatch( STORE_NAME ).setAMPContainerID( CONTAINER_CREATE );
				registry.dispatch( STORE_NAME ).setInternalAMPContainerID( '' );
			} );

			it( 'should display a default container name when nothing is entered yet', async () => {
				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );
				expect( container.querySelector( '#ampContainerName' ) ).toHaveValue( `${ siteName } AMP` );
			} );

			it( 'should use a domain name as a default value when siteName is empty', async () => {
				provideSiteInfo( registry, { siteName: '' } );
				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );
				expect( container.querySelector( '#ampContainerName' ) ).toHaveValue( 'example.com AMP' );
			} );

			it( 'should display an error when a non-unique container name is used', async () => {
				registry.dispatch( CORE_FORMS ).setValues( FORM_SETUP, { ampContainerName: allContainers[ 0 ].name } );

				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );

				expect( container.querySelector( '#ampContainerName' ) ).toHaveValue( allContainers[ 0 ].name );
				expect( container.querySelector( '.googlesitekit-tagmanager-containername > .mdc-text-field' ) ).toHaveClass( 'mdc-text-field--error' );
			} );
		} );

		describe( 'with secondary AMP', () => {
			beforeEach( () => {
				provideSiteInfo( registry, {
					siteName,
					ampMode: AMP_MODE_SECONDARY,
				} );

				registry.dispatch( STORE_NAME ).setContainerID( CONTAINER_CREATE );
				registry.dispatch( STORE_NAME ).setInternalContainerID( '' );

				registry.dispatch( STORE_NAME ).setAMPContainerID( CONTAINER_CREATE );
				registry.dispatch( STORE_NAME ).setInternalAMPContainerID( '' );
			} );

			it( 'should display default container names when nothing is entered yet', async () => {
				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );
				expect( container.querySelector( '#containerName' ) ).toHaveValue( siteName );
				expect( container.querySelector( '#ampContainerName' ) ).toHaveValue( `${ siteName } AMP` );
			} );

			it( 'should use domain name as default values when siteName is empty', async () => {
				provideSiteInfo( registry, { siteName: '' } );
				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );
				expect( container.querySelector( '#containerName' ) ).toHaveValue( 'example.com' );
				expect( container.querySelector( '#ampContainerName' ) ).toHaveValue( 'example.com AMP' );
			} );

			it( 'should display errors when non-unique container names are used', async () => {
				registry.dispatch( CORE_FORMS ).setValues( FORM_SETUP, {
					containerName: allContainers[ 0 ].name,
					ampContainerName: allContainers[ 1 ].name,
				} );

				const { container } = await waitFor( () => render( <SetupMain />, { registry } ) );

				expect( container.querySelector( '#containerName' ) ).toHaveValue( allContainers[ 0 ].name );
				expect( container.querySelector( '.googlesitekit-tagmanager-containerName > .mdc-text-field' ) ).toHaveClass( 'mdc-text-field--error' );

				expect( container.querySelector( '#ampContainerName' ) ).toHaveValue( allContainers[ 1 ].name );
				expect( container.querySelector( '.googlesitekit-tagmanager-ampContainerName > .mdc-text-field' ) ).toHaveClass( 'mdc-text-field--error' );
			} );
		} );
	} );
} );
<|endoftext|>


<commit_before>import SPECS from 'common/SPECS';
import SPEC_ANALYSIS_COMPLETENESS from 'common/SPEC_ANALYSIS_COMPLETENESS';

import CombatLogParser from './CombatLogParser';
import CHANGELOG from './CHANGELOG';

export default {
  spec: SPECS.VENGEANCE_DEMON_HUNTER,
  maintainer: '@Mamtooth',
  completeness: SPEC_ANALYSIS_COMPLETENESS.GOOD, // When changing this please make a PR with ONLY this value changed, we will do a review of your analysis to find out of it is complete enough.
  changelog: CHANGELOG,
  parser: CombatLogParser,
};
<commit_msg>Changed the spec completeness from good to great<commit_after>import SPECS from 'common/SPECS';
import SPEC_ANALYSIS_COMPLETENESS from 'common/SPEC_ANALYSIS_COMPLETENESS';

import CombatLogParser from './CombatLogParser';
import CHANGELOG from './CHANGELOG';

export default {
  spec: SPECS.VENGEANCE_DEMON_HUNTER,
  maintainer: '@Mamtooth',
  completeness: SPEC_ANALYSIS_COMPLETENESS.GREAT, // When changing this please make a PR with ONLY this value changed, we will do a review of your analysis to find out of it is complete enough.
  changelog: CHANGELOG,
  parser: CombatLogParser,
};
<|endoftext|>


<commit_before>// @flow

import type { GlobalTheme } from '../types/themes';
import type { Styles } from '../types/styles';
import type { AppState } from '../redux/redux-setup';

import { StyleSheet } from 'react-native';
import { createSelector } from 'reselect';
import PropTypes from 'prop-types';

import { backgroundIsDarkSelector } from '../selectors/nav-selectors';

const light = Object.freeze({
  redButton: '#BB8888',
  greenButton: '#88BB88',
  mintButton: '#44CC99',
  redText: '#AA0000',
  greenText: 'green',
  link: '#036AFF',
  panelBackground: '#E9E9EF',
  panelBackgroundLabel: '#888888',
  panelForeground: 'white',
  panelForegroundBorder: '#CCCCCC',
  panelForegroundLabel: 'black',
  panelForegroundSecondaryLabel: '#333333',
  panelForegroundTertiaryLabel: '#888888',
  panelIosHighlightUnderlay: '#EEEEEEDD',
  panelSecondaryForeground: '#F5F5F5',
  panelSecondaryForegroundBorder: '#D1D1D6',
  modalForeground: 'white',
  modalForegroundBorder: '#CCCCCC',
  modalForegroundLabel: 'black',
  modalForegroundSecondaryLabel: '#888888',
  modalBackground: '#EEEEEE',
  modalBackgroundLabel: '#333333',
  modalBackgroundSecondaryLabel: '#AAAAAA',
  modalIosHighlightUnderlay: '#CCCCCCDD',
  modalSubtext: '#CCCCCC',
  modalSubtextLabel: '#555555',
  modalButton: '#BBBBBB',
  modalButtonLabel: 'black',
  modalContrastBackground: 'black',
  modalContrastForegroundLabel: 'white',
  modalContrastOpacity: 0.7,
  listForegroundLabel: 'black',
  listForegroundSecondaryLabel: '#333333',
  listForegroundTertiaryLabel: '#666666',
  listForegroundQuaternaryLabel: '#AAAAAA',
  listBackground: 'white',
  listBackgroundLabel: 'black',
  listBackgroundSecondaryLabel: '#444444',
  listBackgroundTernaryLabel: '#999999',
  listSeparator: '#EEEEEE',
  listSeparatorLabel: '#555555',
  listInputBar: '#E2E2E2',
  listInputBorder: '#AAAAAAAA',
  listInputButton: '#888888',
  listInputBackground: '#DDDDDD',
  listIosHighlightUnderlay: '#DDDDDDDD',
  listSearchBackground: '#DDDDDD',
  listSearchIcon: '#AAAAAA',
  listChatBubble: '#DDDDDDBB',
});
export type Colors = $Exact<typeof light>;

const colorsPropType = PropTypes.objectOf(
  PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
);

const dark: Colors = Object.freeze({
  redButton: '#FF4444',
  greenButton: '#44BB44',
  mintButton: '#44CC99',
  redText: '#FF4444',
  greenText: '#44FF44',
  link: '#129AFF',
  panelBackground: '#1C1C1E',
  panelBackgroundLabel: '#C7C7CC',
  panelForeground: '#3A3A3C',
  panelForegroundBorder: '#2C2C2E',
  panelForegroundLabel: 'white',
  panelForegroundSecondaryLabel: '#CCCCCC',
  panelForegroundTertiaryLabel: '#AAAAAA',
  panelIosHighlightUnderlay: '#444444DD',
  panelSecondaryForeground: '#333333',
  panelSecondaryForegroundBorder: '#666666',
  modalForeground: '#1C1C1E',
  modalForegroundBorder: '#1C1C1E',
  modalForegroundLabel: 'white',
  modalForegroundSecondaryLabel: '#AAAAAA',
  modalBackground: '#2C2C2E',
  modalBackgroundLabel: '#CCCCCC',
  modalBackgroundSecondaryLabel: '#555555',
  modalIosHighlightUnderlay: '#AAAAAA88',
  modalSubtext: '#444444',
  modalSubtextLabel: '#AAAAAA',
  modalButton: '#666666',
  modalButtonLabel: 'white',
  modalContrastBackground: 'white',
  modalContrastForegroundLabel: 'black',
  modalContrastOpacity: 0.85,
  listForegroundLabel: 'white',
  listForegroundSecondaryLabel: '#CCCCCC',
  listForegroundTertiaryLabel: '#999999',
  listForegroundQuaternaryLabel: '#555555',
  listBackground: '#1C1C1E',
  listBackgroundLabel: '#C7C7CC',
  listBackgroundSecondaryLabel: '#BBBBBB',
  listBackgroundTernaryLabel: '#555555',
  listSeparator: '#3A3A3C',
  listSeparatorLabel: '#EEEEEE',
  listInputBar: '#555555',
  listInputBorder: '#333333',
  listInputButton: '#AAAAAA',
  listInputBackground: '#38383C',
  listIosHighlightUnderlay: '#BBBBBB88',
  listSearchBackground: '#555555',
  listSearchIcon: '#AAAAAA',
  listChatBubble: '#444444DD',
});
const colors = { light, dark };

const colorsSelector: (state: AppState) => Colors = createSelector(
  (state: AppState) => state.globalThemeInfo.activeTheme,
  (theme: ?GlobalTheme) => {
    const explicitTheme = theme ? theme : 'light';
    return colors[explicitTheme];
  },
);

const overlayColorsSelector: (state: AppState) => Colors = createSelector(
  backgroundIsDarkSelector,
  (backgroundIsDark: bool) => {
    const syntheticTheme = backgroundIsDark ? 'dark' : 'light';
    return colors[syntheticTheme];
  },
);

const magicStrings = new Set();
for (let theme in colors) {
  for (let magicString in colors[theme]) {
    magicStrings.add(magicString);
  }
}

// Distinct type needed here because we replace type of some fields with strings
type InStyles = { [name: string]: { [field: string]: number | string } };

function stylesFromColors<
  IS: InStyles,
  +OS: Styles,
>(obj: IS, themeColors: Colors): OS {
  const result = {};
  for (let key in obj) {
    const style = obj[key];
    const filledInStyle = { ...style };
    for (let styleKey in style) {
      const styleValue = style[styleKey];
      if (magicStrings.has(styleValue)) {
        const mapped = themeColors[styleValue];
        if (mapped) {
          filledInStyle[styleKey] = mapped;
        }
      }
    }
    result[key] = filledInStyle;
  }
  return StyleSheet.create(result);
}

function styleSelector<
  IS: InStyles,
  +OS: Styles,
>(obj: IS): (state: AppState) => OS {
  return createSelector(
    colorsSelector,
    (themeColors: Colors) => stylesFromColors(obj, themeColors),
  );
}

function overlayStyleSelector<
  IS: InStyles,
  +OS: Styles,
>(obj: IS): (state: AppState) => OS {
  return createSelector(
    overlayColorsSelector,
    (themeColors: Colors) => stylesFromColors(obj, themeColors),
  );
}

export {
  colorsPropType,
  colors,
  colorsSelector,
  overlayColorsSelector,
  styleSelector,
  overlayStyleSelector,
};
<commit_msg>[native] Increase dark listBackgroundTernaryLabel contrast<commit_after>// @flow

import type { GlobalTheme } from '../types/themes';
import type { Styles } from '../types/styles';
import type { AppState } from '../redux/redux-setup';

import { StyleSheet } from 'react-native';
import { createSelector } from 'reselect';
import PropTypes from 'prop-types';

import { backgroundIsDarkSelector } from '../selectors/nav-selectors';

const light = Object.freeze({
  redButton: '#BB8888',
  greenButton: '#88BB88',
  mintButton: '#44CC99',
  redText: '#AA0000',
  greenText: 'green',
  link: '#036AFF',
  panelBackground: '#E9E9EF',
  panelBackgroundLabel: '#888888',
  panelForeground: 'white',
  panelForegroundBorder: '#CCCCCC',
  panelForegroundLabel: 'black',
  panelForegroundSecondaryLabel: '#333333',
  panelForegroundTertiaryLabel: '#888888',
  panelIosHighlightUnderlay: '#EEEEEEDD',
  panelSecondaryForeground: '#F5F5F5',
  panelSecondaryForegroundBorder: '#D1D1D6',
  modalForeground: 'white',
  modalForegroundBorder: '#CCCCCC',
  modalForegroundLabel: 'black',
  modalForegroundSecondaryLabel: '#888888',
  modalBackground: '#EEEEEE',
  modalBackgroundLabel: '#333333',
  modalBackgroundSecondaryLabel: '#AAAAAA',
  modalIosHighlightUnderlay: '#CCCCCCDD',
  modalSubtext: '#CCCCCC',
  modalSubtextLabel: '#555555',
  modalButton: '#BBBBBB',
  modalButtonLabel: 'black',
  modalContrastBackground: 'black',
  modalContrastForegroundLabel: 'white',
  modalContrastOpacity: 0.7,
  listForegroundLabel: 'black',
  listForegroundSecondaryLabel: '#333333',
  listForegroundTertiaryLabel: '#666666',
  listForegroundQuaternaryLabel: '#AAAAAA',
  listBackground: 'white',
  listBackgroundLabel: 'black',
  listBackgroundSecondaryLabel: '#444444',
  listBackgroundTernaryLabel: '#999999',
  listSeparator: '#EEEEEE',
  listSeparatorLabel: '#555555',
  listInputBar: '#E2E2E2',
  listInputBorder: '#AAAAAAAA',
  listInputButton: '#888888',
  listInputBackground: '#DDDDDD',
  listIosHighlightUnderlay: '#DDDDDDDD',
  listSearchBackground: '#DDDDDD',
  listSearchIcon: '#AAAAAA',
  listChatBubble: '#DDDDDDBB',
});
export type Colors = $Exact<typeof light>;

const colorsPropType = PropTypes.objectOf(
  PropTypes.oneOfType([ PropTypes.string, PropTypes.number ]),
);

const dark: Colors = Object.freeze({
  redButton: '#FF4444',
  greenButton: '#44BB44',
  mintButton: '#44CC99',
  redText: '#FF4444',
  greenText: '#44FF44',
  link: '#129AFF',
  panelBackground: '#1C1C1E',
  panelBackgroundLabel: '#C7C7CC',
  panelForeground: '#3A3A3C',
  panelForegroundBorder: '#2C2C2E',
  panelForegroundLabel: 'white',
  panelForegroundSecondaryLabel: '#CCCCCC',
  panelForegroundTertiaryLabel: '#AAAAAA',
  panelIosHighlightUnderlay: '#444444DD',
  panelSecondaryForeground: '#333333',
  panelSecondaryForegroundBorder: '#666666',
  modalForeground: '#1C1C1E',
  modalForegroundBorder: '#1C1C1E',
  modalForegroundLabel: 'white',
  modalForegroundSecondaryLabel: '#AAAAAA',
  modalBackground: '#2C2C2E',
  modalBackgroundLabel: '#CCCCCC',
  modalBackgroundSecondaryLabel: '#555555',
  modalIosHighlightUnderlay: '#AAAAAA88',
  modalSubtext: '#444444',
  modalSubtextLabel: '#AAAAAA',
  modalButton: '#666666',
  modalButtonLabel: 'white',
  modalContrastBackground: 'white',
  modalContrastForegroundLabel: 'black',
  modalContrastOpacity: 0.85,
  listForegroundLabel: 'white',
  listForegroundSecondaryLabel: '#CCCCCC',
  listForegroundTertiaryLabel: '#999999',
  listForegroundQuaternaryLabel: '#555555',
  listBackground: '#1C1C1E',
  listBackgroundLabel: '#C7C7CC',
  listBackgroundSecondaryLabel: '#BBBBBB',
  listBackgroundTernaryLabel: '#888888',
  listSeparator: '#3A3A3C',
  listSeparatorLabel: '#EEEEEE',
  listInputBar: '#555555',
  listInputBorder: '#333333',
  listInputButton: '#AAAAAA',
  listInputBackground: '#38383C',
  listIosHighlightUnderlay: '#BBBBBB88',
  listSearchBackground: '#555555',
  listSearchIcon: '#AAAAAA',
  listChatBubble: '#444444DD',
});
const colors = { light, dark };

const colorsSelector: (state: AppState) => Colors = createSelector(
  (state: AppState) => state.globalThemeInfo.activeTheme,
  (theme: ?GlobalTheme) => {
    const explicitTheme = theme ? theme : 'light';
    return colors[explicitTheme];
  },
);

const overlayColorsSelector: (state: AppState) => Colors = createSelector(
  backgroundIsDarkSelector,
  (backgroundIsDark: bool) => {
    const syntheticTheme = backgroundIsDark ? 'dark' : 'light';
    return colors[syntheticTheme];
  },
);

const magicStrings = new Set();
for (let theme in colors) {
  for (let magicString in colors[theme]) {
    magicStrings.add(magicString);
  }
}

// Distinct type needed here because we replace type of some fields with strings
type InStyles = { [name: string]: { [field: string]: number | string } };

function stylesFromColors<
  IS: InStyles,
  +OS: Styles,
>(obj: IS, themeColors: Colors): OS {
  const result = {};
  for (let key in obj) {
    const style = obj[key];
    const filledInStyle = { ...style };
    for (let styleKey in style) {
      const styleValue = style[styleKey];
      if (magicStrings.has(styleValue)) {
        const mapped = themeColors[styleValue];
        if (mapped) {
          filledInStyle[styleKey] = mapped;
        }
      }
    }
    result[key] = filledInStyle;
  }
  return StyleSheet.create(result);
}

function styleSelector<
  IS: InStyles,
  +OS: Styles,
>(obj: IS): (state: AppState) => OS {
  return createSelector(
    colorsSelector,
    (themeColors: Colors) => stylesFromColors(obj, themeColors),
  );
}

function overlayStyleSelector<
  IS: InStyles,
  +OS: Styles,
>(obj: IS): (state: AppState) => OS {
  return createSelector(
    overlayColorsSelector,
    (themeColors: Colors) => stylesFromColors(obj, themeColors),
  );
}

export {
  colorsPropType,
  colors,
  colorsSelector,
  overlayColorsSelector,
  styleSelector,
  overlayStyleSelector,
};
<|endoftext|>


<commit_before>var EMU = 914400; // OfficeXML measures in English Metric Units

module.exports = {

  // assume passed in an array of row objects
  getTable: function(rows, options) {
    var options = options || {};

    if (options.columnWidth === undefined) {
      options.columnWidth = 8 / (rows[0].length) * EMU
    }
    var self = this;

    return self._getBase(
        rows.map(function(row) {
          return self._getRow(
              row.map(function(cell) {
                return self._getCell(cell, options);
              }),
              options
          );
        }),
        self._getColSpecs(rows, options),
        options
    )
  },

  "_getBase": function (rowSpecs, colSpecs, options) {
    var self = this;

    return {
      "p:graphicFrame": {
        "p:nvGraphicFramePr": {
          "p:cNvPr": {
            "@id": "6",
            "@name": "Table 5"
          },
          "p:cNvGraphicFramePr": {
            "a:graphicFrameLocks": {
              "@noGrp": "1"
            }
          },
          "p:nvPr": {
            "p:extLst": {
              "p:ext": {
                "@uri": "{D42A27DB-BD31-4B8C-83A1-F6EECF244321}",
                "p14:modId": {
                  "@xmlns:p14": "http://schemas.microsoft.com/office/powerpoint/2010/main",
                  "@val": "1579011935"
                }
              }
            }
          }
        },
        "p:xfrm": {
          "a:off": {
            "@x": options.x || "1524000",
            "@y": options.y || "1397000"
          },
          "a:ext": {
            "@cx": options.cx || "6096000",
            "@cy": options.cy || "1483360"
          }
        },
        "a:graphic": {
          "a:graphicData": {
            "@uri": "http://schemas.openxmlformats.org/drawingml/2006/table",
            "a:tbl": {
              "a:tblPr": {
                "@firstRow": "1",
                "@bandRow": "1",
                "a:tableStyleId": options.tableStyleId || "{3C2FFA5D-87B4-456A-9821-1D502468CF0F}"
              },
              "a:tblGrid": {
                "#list": colSpecs
              },

              "#list": [rowSpecs]  // replace this with  an array of table row objects
            }
          }
        }
      }
    }
  },

  _getColSpecs: function(rows, options) {
    var self = this;
    return rows[0].map(function(val,idx) {
      return self._tblGrid(idx, options);
    })
  },

  _tblGrid: function(idx, options) {
    return {
      "a:gridCol": {
        "@w": (options.columnWidths ? options.columnWidths[idx] : options.columnWidth||"0" ) //|| "2048000"
      }
    };
  },

  _getRow: function (cells, options) {
    return {
      "a:tr": {
        "@h": options.rowHeight ||"0", //|| "370840",
        "#list": [cells] // populate this with an array of table cell objects
      }
    }
  },

  _getCell: function (val, options) {
    var font_size = options.font_size || 14;
    var font_face = options.font_face || "Times New Roman";
    return {
      "a:tc": {
        "a:txBody": {
          "a:bodyPr": {},
          "a:lstStyle": {},

          "a:p": {
            "a:r": {
              "a:rPr": {
                "@lang": "en-US",
                "@sz": ""+font_size*100,
                "@dirty": "0",
                "@smtClean": "0",
                "a:latin": {
                  "@typeface": font_face
                },
                "a:cs": {
                  "@typeface": font_face
                }
              },
              "a:t": val  // this is the cell value
            },
            "a:endParaRPr": {
              "@lang": "en-US",
              "@sz": ""+font_size*100,
              "@dirty": "0",
              "a:latin": {
                "@typeface": font_face
              },
              "a:cs": {
                "@typeface": font_face
              }
            }
          }
        },
        "a:tcPr": {}
      }
    }
  }
}
<commit_msg>alignmnet and table style<commit_after>var EMU = 914400; // OfficeXML measures in English Metric Units

module.exports = {

  // assume passed in an array of row objects
  getTable: function(rows, options) {
    var options = options || {};
	options.tabstyle=options.tabstyle?options.tabstyle:"{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}"
    if (options.columnWidth === undefined) {
      options.columnWidth = 8 / (rows[0].length) * EMU
    }
    var self = this;

    return self._getBase(
        rows.map(function(row,row_idx) {
          return self._getRow(
              row.map(function(val,idx) {
                return self._getCell(val, options,idx,row_idx);
              }),
              options
          );
        }),
        self._getColSpecs(rows, options),
        options
    )
  },

  "_getBase": function (rowSpecs, colSpecs, options) {
    var self = this;

    return {
      "p:graphicFrame": {
        "p:nvGraphicFramePr": {
          "p:cNvPr": {
            "@id": "6",
            "@name": "Table 5"
          },
          "p:cNvGraphicFramePr": {
            "a:graphicFrameLocks": {
              "@noGrp": "1"
            }
          },
          "p:nvPr": {
            "p:extLst": {
              "p:ext": {
                "@uri": "{D42A27DB-BD31-4B8C-83A1-F6EECF244321}",
                "p14:modId": {
                  "@xmlns:p14": "http://schemas.microsoft.com/office/powerpoint/2010/main",
                  "@val": "1579011935"
                }
              }
            }
          }
        },
        "p:xfrm": {
          "a:off": {
            "@x": options.x || "1524000",
            "@y": options.y || "1397000"
          },
          "a:ext": {
            "@cx": options.cx || "6096000",
            "@cy": options.cy || "1483360"
          }
        },
        "a:graphic": {
          "a:graphicData": {
            "@uri": "http://schemas.openxmlformats.org/drawingml/2006/table",
            "a:tbl": {
              "a:tblPr": {
                "@firstRow": "1",
                "@bandRow": "1",
                "a:tableStyleId":options.tabstyle//"{5C22544A-7EE6-4342-B048-85BDC9FD1C3A}"
              },
              "a:tblGrid": {
                "#list": colSpecs
              },

              "#list": [rowSpecs]  // replace this with  an array of table row objects
            }
          }
        }
      }
    }
  },

  _getColSpecs: function(rows, options) {
    var self = this;
    return rows[0].map(function(val,idx) {
      return self._tblGrid(idx, options);
    })
  },

  _tblGrid: function(idx, options) {
    return {
      "a:gridCol": {
        "@w": (options.columnWidths ? options.columnWidths[idx] : options.columnWidth||"0" ) //|| "2048000"
      }
    };
  },

  _getRow: function (cells, options) {
    return {
      "a:tr": {
        "@h": options.rowHeight ||"0", //|| "370840",
        "#list": [cells] // populate this with an array of table cell objects
      }
    }
  },

  _getCell: function (val, options,idx,row_idx) {
    var font_size = options.font_size || 14;
    var font_face = options.font_face || "Times New Roman";
    return {
      "a:tc": {
        "a:txBody": {
          "a:bodyPr": {},
          "a:lstStyle": {},

          "a:p": {"a:pPr":{"@algn":options.align?(options.align[idx]?options.align[idx]:'ctr'):'ctr'},
            "a:r": {
              "a:rPr": {
                "@lang": "en-US",
                "@sz": ""+font_size*100,
                "@dirty": "0",
                "@smtClean": "0",
                "a:latin": {
                  "@typeface": font_face
                },
                "a:cs": {
                  "@typeface": font_face
                }
              },
              "a:t": val  // this is the cell value
            },
            "a:endParaRPr": {
              "@lang": "en-US",
              "@sz": ""+font_size*100,
              "@dirty": "0",
              "a:latin": {
                "@typeface": font_face
              },
              "a:cs": {
                "@typeface": font_face
              }
            }
          }
        },
        "a:tcPr": {}
      }
    }
  }
}
<|endoftext|>


<commit_before>/* eslint-env node */
'use strict';

module.exports = {
  name: 'ember-toggle',

  included: function(app, parentAddon) {
    var target = (parentAddon || app);

    // necessary for nested usage
    // parent addon should call `this._super.included.apply(this, arguments);`
    if (target.app) {
      target = target.app;
    }

    this.app = target;

    // Use configuration to decide which theme css files
    // to import, thus not populating the user's app
    this.importThemes(target);
    this._super.included.apply(this, arguments);
  },

  importThemes: function(app) {
    var projectConfig = this.project.config(app.env);
    var config = projectConfig['ember-toggle'] || {};
    var themes = [];
    var excludeBaseStyles = false;

    if (config) {
      var allThemes = ['light', 'ios', 'default', 'flat', 'skewed', 'flip', 'material'];
      var included = config.includedThemes;
      var excluded = config.excludedThemes;

      excludeBaseStyles = config.excludeBaseStyles || false;

      if (included && Array.isArray(included)) {
        themes = themes.concat(included);
      }
      else {
        themes = allThemes;
      }

      if (excluded && Array.isArray(excluded)) {
        themes = themes.filter(function (theme) {
          return excluded.indexOf(theme) === -1;
        });
      }

      themes = themes.filter(function (theme) {
        return theme && allThemes.indexOf(theme) !== -1;
      });
    }

    if (!excludeBaseStyles) {
      app.import('vendor/ember-toggle/base.css');
    }

    themes = themes.length ? themes : ['default'];

    themes.forEach(function (theme) {
      app.import('vendor/ember-toggle/themes/' + theme + '.css');
    });
  }
};

<commit_msg>fix(config): Import all themes if user botched included themes<commit_after>/* eslint-env node */
'use strict';

module.exports = {
  name: 'ember-toggle',

  included: function(app, parentAddon) {
    var target = (parentAddon || app);

    // necessary for nested usage
    // parent addon should call `this._super.included.apply(this, arguments);`
    if (target.app) {
      target = target.app;
    }

    this.app = target;

    // Use configuration to decide which theme css files
    // to import, thus not populating the user's app
    this.importThemes(target);
    this._super.included.apply(this, arguments);
  },

  importThemes: function(app) {
    var projectConfig = this.project.config(app.env);
    var config = projectConfig['ember-toggle'] || {};
    var excludeBaseStyles = config.excludeBaseStyles || false;
    var allThemes = ['light', 'ios', 'default', 'flat', 'skewed', 'flip', 'material'];
    var included = config.includedThemes;
    var excluded = config.excludedThemes;
    var themes = [];

    if (included && Array.isArray(included)) {
      themes = themes.concat(included);
    } else {
      themes = allThemes;
    }

    if (excluded && Array.isArray(excluded)) {
      themes = themes.filter(function (theme) {
        return excluded.indexOf(theme) === -1;
      });
    }

    themes = themes.filter(function (theme) {
      return theme && allThemes.indexOf(theme) !== -1;
    });

    if (!excludeBaseStyles) {
      app.import('vendor/ember-toggle/base.css');
    }

    // Include all themes if user incorrectly specified themes in the config
    themes = themes.length ? themes : allThemes;

    themes.forEach(function (theme) {
      app.import('vendor/ember-toggle/themes/' + theme + '.css');
    });
  }
};

<|endoftext|>


<commit_before>/**
 * WordPress dependencies
 */
import domReady from '@wordpress/dom-ready';
import { __, _n, sprintf } from '@wordpress/i18n';

const { ampValidation } = window;

/**
 * The id for the 'Showing x of y errors' notice.
 *
 * @member {string}
 */
const idNumberErrors = 'number-errors';

/**
 * The id for the 'Show all' button.
 *
 * @member {string}
 */
const showAllId = 'show-all-errors';

domReady( () => {
	handleShowAll();
	handleFiltering();
	handleSearching();
	handleStatusChange();
	handleBulkActions();
	changeHeading();
	watchForUnsavedChanges();
	showAMPIconIfEnabled();
} );

let beforeUnloadPromptAdded = false;

/**
 * Add prompt when leaving page due to unsaved changes.
 */
const addBeforeUnloadPrompt = () => {
	if ( beforeUnloadPromptAdded ) {
		return;
	}
	window.addEventListener( 'beforeunload', onBeforeUnload );

	// Remove prompt when clicking trash or update.
	document.querySelector( '#major-publishing-actions' ).addEventListener( 'click', () => {
		window.removeEventListener( 'beforeunload', onBeforeUnload );
	} );

	beforeUnloadPromptAdded = true;
};

/**
 * Watch for unsaved changes.
 *
 * Add an beforeunload warning when attempting to leave the page when there are unsaved changes,
 * unless the user is pressing the trash link or update button.
 */
const watchForUnsavedChanges = () => {
	const onChange = ( event ) => {
		if ( event.target.matches( 'select' ) && event.target.getAttribute( 'id' ) !== 'amp_validation_error_type' ) {
			document.getElementById( 'post' ).removeEventListener( 'change', onChange );
			addBeforeUnloadPrompt();
		}
	};

	document.getElementById( 'post' ).addEventListener( 'change', onChange );
};

/**
 * Show message at beforeunload.
 *
 * @param {Event} event - The beforeunload event.
 * @return {string} Message.
 */
const onBeforeUnload = ( event ) => {
	event.preventDefault();
	event.returnValue = __( 'You have unsaved changes. Are you sure you want to leave?', 'amp' );

	return __( 'You have unsaved changes. Are you sure you want to leave?', 'amp' );
};

/**
 * Updates the <tr> with 'Showing x of y validation errors' at the top of the list table with the current count.
 * If this does not exist yet, it creates the element.
 *
 * @param {number} numberErrorsDisplaying - The number of errors displaying.
 * @param {number} totalErrors - The total number of errors, displaying or not.
 */
const updateShowingErrorsRow = ( numberErrorsDisplaying, totalErrors ) => {
	const showAllButton = document.getElementById( showAllId );
	let thead, th,
		tr = document.getElementById( idNumberErrors );
	const theadQuery = document.getElementsByTagName( 'thead' );

	// Only create the <tr> if it does not exist yet.
	if ( theadQuery[ 0 ] && ! tr ) {
		thead = theadQuery[ 0 ];
		tr = document.createElement( 'tr' );
		th = document.createElement( 'th' );
		th.setAttribute( 'id', idNumberErrors );
		th.setAttribute( 'colspan', '6' );
		tr.appendChild( th );
		thead.appendChild( tr );
	}

	// If all of the errors are displaying, hide the 'Show all' button and the count notice.
	if ( numberErrorsDisplaying === totalErrors ) {
		if ( showAllButton ) {
			showAllButton.classList.add( 'hidden' );
		}
		tr.classList.add( 'hidden' );
	} else if ( null !== numberErrorsDisplaying ) {
		// Update the number of errors displaying and create a 'Show all' button if it does not exist yet.
		document.getElementById( idNumberErrors ).innerText = sprintf(
			/* translators: 1: number of errors being displayed. 2: total number of errors found. */
			_n(
				'Showing %1$s of %2$s validation error',
				'Showing %1$s of %2$s validation errors',
				totalErrors,
				'amp'
			),
			numberErrorsDisplaying,
			totalErrors,
		);
		document.getElementById( idNumberErrors ).classList.remove( 'hidden' );
		conditionallyCreateShowAllButton();
		if ( document.getElementById( showAllId ) ) {
			document.getElementById( showAllId ).classList.remove( 'hidden' );
		}
	}
};

/**
 * Conditionally creates and appends a 'Show all' button.
 */
const conditionallyCreateShowAllButton = () => {
	const buttonContainer = document.getElementById( 'url-post-filter' );
	let showAllButton = document.getElementById( showAllId );

	// There is no 'Show all' <button> yet, but there is a container element for it, create the <button>
	if ( ! showAllButton && buttonContainer ) {
		showAllButton = document.createElement( 'button' );
		showAllButton.id = showAllId;
		showAllButton.classList.add( 'button' );
		showAllButton.innerText = __( 'Show all', 'amp' );

		buttonContainer.appendChild( showAllButton );
	}
};

/**
 * On clicking the 'Show all' <button>, this displays all of the validation errors.
 * Then, it hides this 'Show all' <button> and the notice for the number of errors showing.
 */
const handleShowAll = () => {
	const onClick = ( event ) => {
		if ( ! event.target.matches( '#' + showAllId ) ) {
			return;
		}
		event.preventDefault();

		const validationErrors = document.querySelectorAll( '[data-error-type]' );

		// Iterate through all of the errors, and remove the 'hidden' class.
		validationErrors.forEach( ( element ) => {
			element.parentElement.parentElement.classList.remove( 'hidden' );
		} );

		/*
		 * Update the notice to indicate that all of the errors are displaying.
		 * Like 'Showing 5 of 5 validation errors'.
		 */
		updateShowingErrorsRow( validationErrors.length, validationErrors.length );

		// Hide this 'Show all' button.
		event.target.classList.add( 'hidden' );

		// Change the value of the error type <select> element to 'All Error Types'.
		document.getElementById( 'amp_validation_error_type' ).selectedIndex = 0;
	};

	document.getElementById( 'url-post-filter' ).addEventListener( 'click', onClick );
};

/**
 * Handles filtering by error type, triggered by clicking 'Apply Filter'.
 *
 * Gets the value of the error type <select> element.
 * And hides all <tr> elements that do not have the same type of this value.
 * If 'All Error Types' is selected, this displays all errors.
 */
const handleFiltering = () => {
	const onChange = ( event ) => {
		if ( ! event.target.matches( 'select' ) ) {
			return;
		}

		event.preventDefault();

		const showAllButton = document.getElementById( showAllId );

		const isAllErrorTypesSelected = ( '-1' === event.target.value );
		const errorTypeQuery = document.querySelectorAll( '[data-error-type]' );

		// If the user has chosen 'All Error Types' from the <select>, hide the 'Show all' button.
		if ( isAllErrorTypesSelected && showAllButton ) {
			showAllButton.classList.add( 'hidden' );
		}

		/*
		 * Iterate through all of the <tr> elements in the list table.
		 * If the error type does not match the value (selected error type), hide them.
		 */
		let numberErrorsDisplaying = 0;
		errorTypeQuery.forEach( ( element ) => {
			const errorType = element.getAttribute( 'data-error-type' );

			// If 'All Error Types' was selected, this should display all errors.
			if ( isAllErrorTypesSelected || ! event.target.value || event.target.value === errorType ) {
				element.parentElement.parentElement.classList.remove( 'hidden' );
				numberErrorsDisplaying++;
			} else {
				element.parentElement.parentElement.classList.add( 'hidden' );
			}
		} );

		updateShowingErrorsRow( numberErrorsDisplaying, errorTypeQuery.length );
	};

	document.getElementById( 'amp_validation_error_type' ).addEventListener( 'change', onChange );
};

/**
 * Handles searching for errors via the <input> and the 'Search Errors' <button>.
 */
const handleSearching = () => {
	const onClick = ( event ) => {
		event.preventDefault();
		if ( ! event.target.matches( 'input' ) ) {
			return;
		}

		const searchQuery = document.getElementById( 'invalid-url-search-search-input' ).value;
		const detailsQuery = document.querySelectorAll( 'tbody .column-details' );

		/*
		 * Iterate through the 'Context' (formerly 'Details') column of each row.
		 * If the search query is not present, hide the row.
		 */
		let numberErrorsDisplaying = 0;
		detailsQuery.forEach( ( element ) => {
			let isSearchQueryPresent = false;

			element.querySelectorAll( '.detailed' ).forEach( ( detailed ) => {
				if ( -1 !== detailed.innerText.indexOf( searchQuery ) ) {
					isSearchQueryPresent = true;
				}
			} );

			if ( isSearchQueryPresent ) {
				element.parentElement.classList.remove( 'hidden' );
				numberErrorsDisplaying++;
			} else {
				element.parentElement.classList.add( 'hidden' );
			}
		} );

		updateShowingErrorsRow( numberErrorsDisplaying, detailsQuery.length );
	};

	document.getElementById( 'search-submit' ).addEventListener( 'click', onClick );
};

/**
 * Update icon for select element.
 *
 * @param {HTMLSelectElement} select Select element.
 */
const updateSelectIcon = ( select ) => {
	const newOption = select.options[ select.selectedIndex ];
	if ( newOption ) {
		const iconSrc = newOption.getAttribute( 'data-status-icon' );
		select.parentNode.querySelector( 'img' ).setAttribute( 'src', iconSrc );
	}
};

/**
 * Handles a change in the error status, like from 'New' to 'Accepted'.
 *
 * Gets the data-status-icon value from the newly-selected <option>.
 * And sets this as the src of the status icon <img>.
 */
const handleStatusChange = () => {
	const setRowStatusClass = ( { row, select } ) => {
		const acceptedValue = 3;
		const rejectedValue = 2;
		const status = parseInt( select.options[ select.selectedIndex ].value );

		row.classList.toggle( 'new', isNaN( status ) );
		row.classList.toggle( 'accepted', acceptedValue === status );
		row.classList.toggle( 'rejected', rejectedValue === status );
	};

	const onChange = ( { event, row, select } ) => {
		if ( event.target.matches( 'select' ) ) {
			updateSelectIcon( event.target );
			setRowStatusClass( { row, select } );
		}
	};

	document.querySelectorAll( 'tr[id^="tag-"]' ).forEach( ( row ) => {
		const select = row.querySelector( '.amp-validation-error-status' );

		if ( select ) {
			setRowStatusClass( { row, select } );
			select.addEventListener( 'change', ( event ) => {
				onChange( { event, row, select } );
			} );
		}
	} );
};

/**
 * On checking a bulk action checkbox, this ensures that the 'Accept' and 'Reject' buttons are present. Handle clicking on buttons.
 *
 * They're hidden until one of these boxes is checked.
 * Also, on unchecking the last checked box, this hides these buttons.
 */
const handleBulkActions = () => {
	const acceptButton = document.querySelector( 'button.action.accept' );
	const rejectButton = document.querySelector( 'button.action.reject' );
	const acceptAndRejectContainer = document.getElementById( 'accept-reject-buttons' );

	const onChange = ( event ) => {
		let areThereCheckedBoxes;

		if ( ! event.target.matches( '[type=checkbox]' ) ) {
			return;
		}

		if ( event.target.checked ) {
			// This checkbox was checked, so ensure the buttons display.
			acceptAndRejectContainer.classList.remove( 'hidden' );
		} else {
			/*
			 * This checkbox was unchecked.
			 * So find if there are any other checkboxes that are checked.
			 * If not, hide the 'Accept' and 'Reject' buttons.
			 */
			areThereCheckedBoxes = false;
			document.querySelectorAll( '.check-column [type=checkbox]' ).forEach( ( element ) => {
				if ( element.checked ) {
					areThereCheckedBoxes = true;
				}
			} );
			if ( ! areThereCheckedBoxes ) {
				acceptAndRejectContainer.classList.add( 'hidden' );
			}
		}
	};

	document.querySelectorAll( '.check-column [type=checkbox]' ).forEach( ( element ) => {
		element.addEventListener( 'change', onChange );
	} );

	// Handle click on accept button.
	acceptButton.addEventListener( 'click', () => {
		Array.prototype.forEach.call( document.querySelectorAll( 'select.amp-validation-error-status' ), ( select ) => {
			if ( select.closest( 'tr' ).querySelector( '.check-column input[type=checkbox]' ).checked ) {
				select.value = '3';
				updateSelectIcon( select );
				addBeforeUnloadPrompt();
			}
		} );
	} );

	// Handle click on reject button.
	rejectButton.addEventListener( 'click', () => {
		Array.prototype.forEach.call( document.querySelectorAll( 'select.amp-validation-error-status' ), ( select ) => {
			if ( select.closest( 'tr' ).querySelector( '.check-column input[type=checkbox]' ).checked ) {
				select.value = '2';
				updateSelectIcon( select );
				addBeforeUnloadPrompt();
			}
		} );
	} );
};

/**
 * Changes the page heading and document title, as this doesn't look to be possible with a PHP filter.
 */
const changeHeading = () => {
	const heading = document.querySelector( 'h1.wp-heading-inline' );
	if ( heading && ampValidation.page_heading ) {
		heading.innerText = ampValidation.page_heading;
	}
};

/**
 * Adds the AMP icon to the page heading if AMP is enabled on this URL.
 */
const showAMPIconIfEnabled = () => {
	const heading = document.querySelector( 'h1.wp-heading-inline' );
	if ( heading && true === Boolean( ampValidation.amp_enabled ) ) {
		const ampIcon = document.createElement( 'span' );
		ampIcon.classList.add( 'status-text', 'sanitized' );
		heading.appendChild( ampIcon );
	}
};
<commit_msg>Ensure the new state is removed from rows when clicking Accept/Reject buttons<commit_after>/**
 * WordPress dependencies
 */
import domReady from '@wordpress/dom-ready';
import { __, _n, sprintf } from '@wordpress/i18n';

const { ampValidation } = window;

/**
 * The id for the 'Showing x of y errors' notice.
 *
 * @member {string}
 */
const idNumberErrors = 'number-errors';

/**
 * The id for the 'Show all' button.
 *
 * @member {string}
 */
const showAllId = 'show-all-errors';

domReady( () => {
	handleShowAll();
	handleFiltering();
	handleSearching();
	handleStatusChange();
	handleBulkActions();
	changeHeading();
	watchForUnsavedChanges();
	showAMPIconIfEnabled();
} );

let beforeUnloadPromptAdded = false;

/**
 * Add prompt when leaving page due to unsaved changes.
 */
const addBeforeUnloadPrompt = () => {
	if ( beforeUnloadPromptAdded ) {
		return;
	}
	window.addEventListener( 'beforeunload', onBeforeUnload );

	// Remove prompt when clicking trash or update.
	document.querySelector( '#major-publishing-actions' ).addEventListener( 'click', () => {
		window.removeEventListener( 'beforeunload', onBeforeUnload );
	} );

	beforeUnloadPromptAdded = true;
};

/**
 * Watch for unsaved changes.
 *
 * Add an beforeunload warning when attempting to leave the page when there are unsaved changes,
 * unless the user is pressing the trash link or update button.
 */
const watchForUnsavedChanges = () => {
	const onChange = ( event ) => {
		if ( event.target.matches( 'select' ) && event.target.getAttribute( 'id' ) !== 'amp_validation_error_type' ) {
			document.getElementById( 'post' ).removeEventListener( 'change', onChange );
			addBeforeUnloadPrompt();
		}
	};

	document.getElementById( 'post' ).addEventListener( 'change', onChange );
};

/**
 * Show message at beforeunload.
 *
 * @param {Event} event - The beforeunload event.
 * @return {string} Message.
 */
const onBeforeUnload = ( event ) => {
	event.preventDefault();
	event.returnValue = __( 'You have unsaved changes. Are you sure you want to leave?', 'amp' );

	return __( 'You have unsaved changes. Are you sure you want to leave?', 'amp' );
};

/**
 * Updates the <tr> with 'Showing x of y validation errors' at the top of the list table with the current count.
 * If this does not exist yet, it creates the element.
 *
 * @param {number} numberErrorsDisplaying - The number of errors displaying.
 * @param {number} totalErrors - The total number of errors, displaying or not.
 */
const updateShowingErrorsRow = ( numberErrorsDisplaying, totalErrors ) => {
	const showAllButton = document.getElementById( showAllId );
	let thead, th,
		tr = document.getElementById( idNumberErrors );
	const theadQuery = document.getElementsByTagName( 'thead' );

	// Only create the <tr> if it does not exist yet.
	if ( theadQuery[ 0 ] && ! tr ) {
		thead = theadQuery[ 0 ];
		tr = document.createElement( 'tr' );
		th = document.createElement( 'th' );
		th.setAttribute( 'id', idNumberErrors );
		th.setAttribute( 'colspan', '6' );
		tr.appendChild( th );
		thead.appendChild( tr );
	}

	// If all of the errors are displaying, hide the 'Show all' button and the count notice.
	if ( numberErrorsDisplaying === totalErrors ) {
		if ( showAllButton ) {
			showAllButton.classList.add( 'hidden' );
		}
		tr.classList.add( 'hidden' );
	} else if ( null !== numberErrorsDisplaying ) {
		// Update the number of errors displaying and create a 'Show all' button if it does not exist yet.
		document.getElementById( idNumberErrors ).innerText = sprintf(
			/* translators: 1: number of errors being displayed. 2: total number of errors found. */
			_n(
				'Showing %1$s of %2$s validation error',
				'Showing %1$s of %2$s validation errors',
				totalErrors,
				'amp'
			),
			numberErrorsDisplaying,
			totalErrors,
		);
		document.getElementById( idNumberErrors ).classList.remove( 'hidden' );
		conditionallyCreateShowAllButton();
		if ( document.getElementById( showAllId ) ) {
			document.getElementById( showAllId ).classList.remove( 'hidden' );
		}
	}
};

/**
 * Conditionally creates and appends a 'Show all' button.
 */
const conditionallyCreateShowAllButton = () => {
	const buttonContainer = document.getElementById( 'url-post-filter' );
	let showAllButton = document.getElementById( showAllId );

	// There is no 'Show all' <button> yet, but there is a container element for it, create the <button>
	if ( ! showAllButton && buttonContainer ) {
		showAllButton = document.createElement( 'button' );
		showAllButton.id = showAllId;
		showAllButton.classList.add( 'button' );
		showAllButton.innerText = __( 'Show all', 'amp' );

		buttonContainer.appendChild( showAllButton );
	}
};

/**
 * On clicking the 'Show all' <button>, this displays all of the validation errors.
 * Then, it hides this 'Show all' <button> and the notice for the number of errors showing.
 */
const handleShowAll = () => {
	const onClick = ( event ) => {
		if ( ! event.target.matches( '#' + showAllId ) ) {
			return;
		}
		event.preventDefault();

		const validationErrors = document.querySelectorAll( '[data-error-type]' );

		// Iterate through all of the errors, and remove the 'hidden' class.
		validationErrors.forEach( ( element ) => {
			element.parentElement.parentElement.classList.remove( 'hidden' );
		} );

		/*
		 * Update the notice to indicate that all of the errors are displaying.
		 * Like 'Showing 5 of 5 validation errors'.
		 */
		updateShowingErrorsRow( validationErrors.length, validationErrors.length );

		// Hide this 'Show all' button.
		event.target.classList.add( 'hidden' );

		// Change the value of the error type <select> element to 'All Error Types'.
		document.getElementById( 'amp_validation_error_type' ).selectedIndex = 0;
	};

	document.getElementById( 'url-post-filter' ).addEventListener( 'click', onClick );
};

/**
 * Handles filtering by error type, triggered by clicking 'Apply Filter'.
 *
 * Gets the value of the error type <select> element.
 * And hides all <tr> elements that do not have the same type of this value.
 * If 'All Error Types' is selected, this displays all errors.
 */
const handleFiltering = () => {
	const onChange = ( event ) => {
		if ( ! event.target.matches( 'select' ) ) {
			return;
		}

		event.preventDefault();

		const showAllButton = document.getElementById( showAllId );

		const isAllErrorTypesSelected = ( '-1' === event.target.value );
		const errorTypeQuery = document.querySelectorAll( '[data-error-type]' );

		// If the user has chosen 'All Error Types' from the <select>, hide the 'Show all' button.
		if ( isAllErrorTypesSelected && showAllButton ) {
			showAllButton.classList.add( 'hidden' );
		}

		/*
		 * Iterate through all of the <tr> elements in the list table.
		 * If the error type does not match the value (selected error type), hide them.
		 */
		let numberErrorsDisplaying = 0;
		errorTypeQuery.forEach( ( element ) => {
			const errorType = element.getAttribute( 'data-error-type' );

			// If 'All Error Types' was selected, this should display all errors.
			if ( isAllErrorTypesSelected || ! event.target.value || event.target.value === errorType ) {
				element.parentElement.parentElement.classList.remove( 'hidden' );
				numberErrorsDisplaying++;
			} else {
				element.parentElement.parentElement.classList.add( 'hidden' );
			}
		} );

		updateShowingErrorsRow( numberErrorsDisplaying, errorTypeQuery.length );
	};

	document.getElementById( 'amp_validation_error_type' ).addEventListener( 'change', onChange );
};

/**
 * Handles searching for errors via the <input> and the 'Search Errors' <button>.
 */
const handleSearching = () => {
	const onClick = ( event ) => {
		event.preventDefault();
		if ( ! event.target.matches( 'input' ) ) {
			return;
		}

		const searchQuery = document.getElementById( 'invalid-url-search-search-input' ).value;
		const detailsQuery = document.querySelectorAll( 'tbody .column-details' );

		/*
		 * Iterate through the 'Context' (formerly 'Details') column of each row.
		 * If the search query is not present, hide the row.
		 */
		let numberErrorsDisplaying = 0;
		detailsQuery.forEach( ( element ) => {
			let isSearchQueryPresent = false;

			element.querySelectorAll( '.detailed' ).forEach( ( detailed ) => {
				if ( -1 !== detailed.innerText.indexOf( searchQuery ) ) {
					isSearchQueryPresent = true;
				}
			} );

			if ( isSearchQueryPresent ) {
				element.parentElement.classList.remove( 'hidden' );
				numberErrorsDisplaying++;
			} else {
				element.parentElement.classList.add( 'hidden' );
			}
		} );

		updateShowingErrorsRow( numberErrorsDisplaying, detailsQuery.length );
	};

	document.getElementById( 'search-submit' ).addEventListener( 'click', onClick );
};

/**
 * Update icon for select element.
 *
 * @param {HTMLSelectElement} select Select element.
 */
const updateSelectIcon = ( select ) => {
	const newOption = select.options[ select.selectedIndex ];
	if ( newOption ) {
		const iconSrc = newOption.getAttribute( 'data-status-icon' );
		select.parentNode.querySelector( 'img' ).setAttribute( 'src', iconSrc );
	}
};

/**
 * Set the row status class.
 *
 * @param {HTMLSelectElement} select Select element.
 */
const setRowStatusClass = ( select ) => {
	const acceptedValue = 3;
	const rejectedValue = 2;
	const status = parseInt( select.options[ select.selectedIndex ].value );
	const row = select.closest( 'tr' );

	row.classList.toggle( 'new', isNaN( status ) );
	row.classList.toggle( 'accepted', acceptedValue === status );
	row.classList.toggle( 'rejected', rejectedValue === status );
};

/**
 * Handles a change in the error status, like from 'New' to 'Accepted'.
 *
 * Gets the data-status-icon value from the newly-selected <option>.
 * And sets this as the src of the status icon <img>.
 */
const handleStatusChange = () => {
	const onChange = ( { event, select } ) => {
		if ( event.target.matches( 'select' ) ) {
			updateSelectIcon( event.target );
			setRowStatusClass( select );
		}
	};

	document.querySelectorAll( 'tr[id^="tag-"]' ).forEach( ( row ) => {
		const select = row.querySelector( '.amp-validation-error-status' );

		if ( select ) {
			setRowStatusClass( /** @type {HTMLSelectElement} */ select );
			select.addEventListener( 'change', ( event ) => {
				onChange( { event, row, select } );
			} );
		}
	} );
};

/**
 * On checking a bulk action checkbox, this ensures that the 'Accept' and 'Reject' buttons are present. Handle clicking on buttons.
 *
 * They're hidden until one of these boxes is checked.
 * Also, on unchecking the last checked box, this hides these buttons.
 */
const handleBulkActions = () => {
	const acceptButton = document.querySelector( 'button.action.accept' );
	const rejectButton = document.querySelector( 'button.action.reject' );
	const acceptAndRejectContainer = document.getElementById( 'accept-reject-buttons' );

	const onChange = ( event ) => {
		let areThereCheckedBoxes;

		if ( ! event.target.matches( '[type=checkbox]' ) ) {
			return;
		}

		if ( event.target.checked ) {
			// This checkbox was checked, so ensure the buttons display.
			acceptAndRejectContainer.classList.remove( 'hidden' );
		} else {
			/*
			 * This checkbox was unchecked.
			 * So find if there are any other checkboxes that are checked.
			 * If not, hide the 'Accept' and 'Reject' buttons.
			 */
			areThereCheckedBoxes = false;
			document.querySelectorAll( '.check-column [type=checkbox]' ).forEach( ( element ) => {
				if ( element.checked ) {
					areThereCheckedBoxes = true;
				}
			} );
			if ( ! areThereCheckedBoxes ) {
				acceptAndRejectContainer.classList.add( 'hidden' );
			}
		}
	};

	document.querySelectorAll( '.check-column [type=checkbox]' ).forEach( ( element ) => {
		element.addEventListener( 'change', onChange );
	} );

	// Handle click on accept button.
	acceptButton.addEventListener( 'click', () => {
		Array.prototype.forEach.call( document.querySelectorAll( 'select.amp-validation-error-status' ), ( select ) => {
			if ( select.closest( 'tr' ).querySelector( '.check-column input[type=checkbox]' ).checked ) {
				select.value = '3';
				updateSelectIcon( select );
				setRowStatusClass( select );
				addBeforeUnloadPrompt();
			}
		} );
	} );

	// Handle click on reject button.
	rejectButton.addEventListener( 'click', () => {
		Array.prototype.forEach.call( document.querySelectorAll( 'select.amp-validation-error-status' ), ( select ) => {
			if ( select.closest( 'tr' ).querySelector( '.check-column input[type=checkbox]' ).checked ) {
				select.value = '2';
				updateSelectIcon( select );
				setRowStatusClass( select );
				addBeforeUnloadPrompt();
			}
		} );
	} );
};

/**
 * Changes the page heading and document title, as this doesn't look to be possible with a PHP filter.
 */
const changeHeading = () => {
	const heading = document.querySelector( 'h1.wp-heading-inline' );
	if ( heading && ampValidation.page_heading ) {
		heading.innerText = ampValidation.page_heading;
	}
};

/**
 * Adds the AMP icon to the page heading if AMP is enabled on this URL.
 */
const showAMPIconIfEnabled = () => {
	const heading = document.querySelector( 'h1.wp-heading-inline' );
	if ( heading && true === Boolean( ampValidation.amp_enabled ) ) {
		const ampIcon = document.createElement( 'span' );
		ampIcon.classList.add( 'status-text', 'sanitized' );
		heading.appendChild( ampIcon );
	}
};
<|endoftext|>


<commit_before>var mongoose = require('mongoose')
  , ObjectId = mongoose.Types.ObjectId;

var helper = {
  getObjectID: function(obj){
    if      (obj === null || typeof obj === 'undefined')         return null;
    else if (typeof obj === 'string')                            return new ObjectId(obj);
    else if (obj.constructor.name === 'ObjectID')                return obj;
    else if (obj._id && obj._id.constructor.name === 'ObjectID') return obj._id;
    else                                                         return null;
  },

  eqObjectIDs: function(user1, user2){
    return helper.getObjectID(user1).toString() === helper.getObjectID(user2).toString();
  },

  containsObject: function(arr, user){
    for (var i = arr.length-1; i >= 0; i--)
      if (helper.eqObjectIDs(arr[i], user)) return true;
    return false;
  }

};

module.exports = helper;

<commit_msg>.containsObject checks for a null or empty array<commit_after>var mongoose = require('mongoose')
  , ObjectId = mongoose.Types.ObjectId;

var helper = {
  getObjectID: function(obj){
    if      (obj === null || typeof obj === 'undefined')         return null;
    else if (typeof obj === 'string')                            return new ObjectId(obj);
    else if (obj.constructor.name === 'ObjectID')                return obj;
    else if (obj._id && obj._id.constructor.name === 'ObjectID') return obj._id;
    else                                                         return null;
  },

  eqObjectIDs: function(user1, user2){
    return helper.getObjectID(user1).toString() === helper.getObjectID(user2).toString();
  },

  containsObject: function(arr, user){
    if (!arr || arr.length === 0) return false;
    for (var i = arr.length-1; i >= 0; i--)
      if (helper.eqObjectIDs(arr[i], user)) return true;
    return false;
  }

};

module.exports = helper;

<|endoftext|>