/** * Karma middleware is responsible for serving: * - client.html (the entrypoint for capturing a browser) * - debug.html * - context.html (the execution context, loaded within an iframe) * - karma.js * * The main part is generating context.html, as it contains: * - generating mappings * - including ' var CROSSORIGIN_ATTRIBUTE = 'crossorigin="anonymous"' var LINK_TAG_CSS = '' var LINK_TAG_HTML = '' var SCRIPT_TYPE = { '.js': 'text/javascript', '.dart': 'application/dart' } var filePathToUrlPath = function (filePath, basePath, urlRoot, proxyPath) { if (filePath.indexOf(basePath) === 0) { return proxyPath + urlRoot.substr(1) + 'base' + filePath.substr(basePath.length) } return proxyPath + urlRoot.substr(1) + 'absolute' + filePath } var getXUACompatibleMetaElement = function (url) { var tag = '' var urlObj = urlparse(url) if (urlObj.query['x-ua-compatible']) { tag = '\n' } return tag } var getXUACompatibleUrl = function (url) { var value = '' var urlObj = urlparse(url) if (urlObj.query['x-ua-compatible']) { value = '?x-ua-compatible=' + encodeURIComponent(urlObj.query['x-ua-compatible']) } return value } var isFirefox = function (req) { if (!(req && req.headers)) { return false } // Browser check var firefox = useragent.is(req.headers['user-agent']).firefox return firefox } var createKarmaMiddleware = function ( filesPromise, serveStaticFile, serveFile, injector, basePath, urlRoot, upstreamProxy ) { var proxyPath = upstreamProxy ? upstreamProxy.path : '/' return function (request, response, next) { // These config values should be up to date on every request var client = injector.get('config.client') var customContextFile = injector.get('config.customContextFile') var customDebugFile = injector.get('config.customDebugFile') var jsVersion = injector.get('config.jsVersion') var includeCrossOriginAttribute = injector.get('config.crossOriginAttribute') var requestUrl = request.normalizedUrl.replace(/\?.*/, '') var requestedRangeHeader = request.headers['range'] // redirect /__karma__ to /__karma__ (trailing slash) if (requestUrl === urlRoot.substr(0, urlRoot.length - 1)) { response.setHeader('Location', proxyPath + urlRoot.substr(1)) response.writeHead(301) return response.end('MOVED PERMANENTLY') } // ignore urls outside urlRoot if (requestUrl.indexOf(urlRoot) !== 0) { return next() } // remove urlRoot prefix requestUrl = requestUrl.substr(urlRoot.length - 1) // serve client.html if (requestUrl === '/') { return serveStaticFile('/client.html', requestedRangeHeader, response, function (data) { return data .replace('\n%X_UA_COMPATIBLE%', getXUACompatibleMetaElement(request.url)) .replace('%X_UA_COMPATIBLE_URL%', getXUACompatibleUrl(request.url)) }) } // serve karma.js, context.js, and debug.js var jsFiles = ['/karma.js', '/context.js', '/debug.js'] var isRequestingJsFile = jsFiles.indexOf(requestUrl) !== -1 if (isRequestingJsFile) { return serveStaticFile(requestUrl, requestedRangeHeader, response, function (data) { return data.replace('%KARMA_URL_ROOT%', urlRoot) .replace('%KARMA_VERSION%', VERSION) .replace('%KARMA_PROXY_PATH%', proxyPath) }) } // serve the favicon if (requestUrl === '/favicon.ico') { return serveStaticFile(requestUrl, requestedRangeHeader, response) } // serve context.html - execution context within the iframe // or debug.html - execution context without channel to the server var isRequestingContextFile = requestUrl === '/context.html' var isRequestingDebugFile = requestUrl === '/debug.html' if (isRequestingContextFile || isRequestingDebugFile) { return filesPromise.then(function (files) { var fileServer var requestedFileUrl log.debug('custom files', customContextFile, customDebugFile) if (isRequestingContextFile && customContextFile) { log.debug('Serving customContextFile %s', customContextFile) fileServer = serveFile requestedFileUrl = customContextFile } else if (isRequestingDebugFile && customDebugFile) { log.debug('Serving customDebugFile %s', customDebugFile) fileServer = serveFile requestedFileUrl = customDebugFile } else { log.debug('Serving static request %s', requestUrl) fileServer = serveStaticFile requestedFileUrl = requestUrl } fileServer(requestedFileUrl, requestedRangeHeader, response, function (data) { common.setNoCacheHeaders(response) var scriptTags = files.included.map(function (file) { var filePath = file.path var fileExt = path.extname(filePath) if (!file.isUrl) { filePath = filePathToUrlPath(filePath, basePath, urlRoot, proxyPath) if (requestUrl === '/context.html') { filePath += '?' + file.sha } } if (fileExt === '.css') { return util.format(LINK_TAG_CSS, filePath) } if (fileExt === '.html') { return util.format(LINK_TAG_HTML, filePath) } // The script tag to be placed var scriptType = (SCRIPT_TYPE[fileExt] || 'text/javascript') // In case there is a JavaScript version specified and this is a Firefox browser if (jsVersion && jsVersion > 0 && isFirefox(request)) { scriptType += ';version=' + jsVersion } var crossOriginAttribute = includeCrossOriginAttribute ? CROSSORIGIN_ATTRIBUTE : '' return util.format(SCRIPT_TAG, scriptType, filePath, crossOriginAttribute) }) // TODO(vojta): don't compute if it's not in the template var mappings = files.served.map(function (file) { // Windows paths contain backslashes and generate bad IDs if not escaped var filePath = filePathToUrlPath(file.path, basePath, urlRoot, proxyPath).replace(/\\/g, '\\\\') // Escape single quotes that might be in the filename - // double quotes should not be allowed! filePath = filePath.replace(/'/g, '\\\'') return util.format(" '%s': '%s'", filePath, file.sha) }) var clientConfig = 'window.__karma__.config = ' + JSON.stringify(client) + ';\n' mappings = 'window.__karma__.files = {\n' + mappings.join(',\n') + '\n};\n' return data .replace('%SCRIPTS%', scriptTags.join('\n')) .replace('%CLIENT_CONFIG%', clientConfig) .replace('%MAPPINGS%', mappings) .replace('\n%X_UA_COMPATIBLE%', getXUACompatibleMetaElement(request.url)) }) }, function (errorFiles) { serveStaticFile(requestUrl, response, function (data) { common.setNoCacheHeaders(response) return data.replace('%SCRIPTS%', '').replace('%CLIENT_CONFIG%', '').replace('%MAPPINGS%', 'window.__karma__.error("TEST RUN WAS CANCELLED because ' + (errorFiles.length > 1 ? 'these files contain' : 'this file contains') + ' some errors:\\n ' + errorFiles.join('\\n ') + '");') }) }) } else if (requestUrl === '/context.json') { return filesPromise.then(function (files) { common.setNoCacheHeaders(response) response.writeHead(200) response.end(JSON.stringify({ files: files.included.map(function (file) { return filePathToUrlPath(file.path + '?' + file.sha, basePath, urlRoot, proxyPath) }) })) }) } return next() } } createKarmaMiddleware.$inject = [ 'filesPromise', 'serveStaticFile', 'serveFile', 'injector', 'config.basePath', 'config.urlRoot', 'config.upstreamProxy' ] // PUBLIC API exports.create = createKarmaMiddleware