<commit_before>/*
 Copyright 2014 OpenMarket Ltd

 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

 http://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.
 */

'use strict';

angular.module('matrixFilter', [])

// Compute the room name according to information we have
.filter('mRoomName', ['$rootScope', 'matrixService', function($rootScope, matrixService) {
    return function(room_id) {
        var roomName;

        // If there is an alias, use it
        // TODO: only one alias is managed for now
        var alias = matrixService.getRoomIdToAliasMapping(room_id);

        var room = $rootScope.events.rooms[room_id];
        if (room) {
            // Get name from room state date
            var room_name_event = room["m.room.name"];
            if (room_name_event) {
                roomName = room_name_event.content.name;
            }
            else if (alias) {
                roomName = alias;
            }
            else if (room.members) {

                var user_id = matrixService.config().user_id;

                // Else, build the name from its users
                // Limit the room renaming to 1:1 room
                if (2 === Object.keys(room.members).length) {
                    for (var i in room.members) {
                        var member = room.members[i];
                        if (member.state_key !== user_id) {

                            if (member.state_key in $rootScope.presence) {
                                // If the user is listed in presence, use the displayname there
                                // as it is the most uptodate
                                roomName = $rootScope.presence[member.state_key].content.displayname || member.state_key;
                            }
                            else {
                                roomName = member.content.displayname || member.state_key;
                            }
                        }
                    }
                }
                else if (1 === Object.keys(room.members).length) {
                    var otherUserId;

                    if (Object.keys(room.members)[0] !== user_id) {
                        otherUserId = Object.keys(room.members)[0];
                    }
                    else {
                        // The other member may be in the invite list, get all invited users
                        var invitedUserIDs = [];

                        // XXX: *SURELY* we shouldn't have to trawl through the whole messages list to
                        // find invite - surely the other user should be in room.members with state invited? :/ --Matthew
                        for (var i in room.messages) {
                            var message = room.messages[i];
                            if ("m.room.member" === message.type && "invite" === message.content.membership) {
                                // Filter out the current user
                                var member_id = message.state_key;
                                if (member_id === user_id) {
                                    member_id = message.user_id;
                                }
                                if (member_id !== user_id) {
                                    // Make sure there is no duplicate user
                                    if (-1 === invitedUserIDs.indexOf(member_id)) {
                                        invitedUserIDs.push(member_id);
                                    }
                                }
                            }
                        }

                        // For now, only 1:1 room needs to be renamed. It means only 1 invited user
                        if (1 === invitedUserIDs.length) {
                            otherUserId = invitedUserIDs[0];
                        }
                    }

                    if (!otherUserId) otherUserId = user_id; // name the room after ourselves as we're the only person there!

                    // Try to resolve his displayname in presence global data
                    if (otherUserId in $rootScope.presence) {
                        roomName = $rootScope.presence[otherUserId].content.displayname;
                    }
                    else {
                        roomName = otherUserId;
                    }
                }
            }
        }

        // Always show the alias in the room displayed name
        if (roomName && alias && alias !== roomName) {
            roomName += " (" + alias + ")";
        }

        if (undefined === roomName) {
            // By default, use the room ID
            roomName = room_id;

            // XXX: this is *INCREDIBLY* heavy logging for a function that calls every single
            // time any kind of digest runs which refreshes a room name...
            // commenting it out for now.

            // Log some information that lead to this leak
            // console.log("Room ID leak for " + room_id);
            // console.log("room object: " + JSON.stringify(room, undefined, 4));
        }

        return roomName;
    };
}])

// Compute the user display name in a room according to the data already downloaded
.filter('mUserDisplayName', ['$rootScope', function($rootScope) {
    return function(user_id, room_id) {
        var displayName;

        // Try to find the user name among presence data
        // Warning: that means we have received before a presence event for this
        // user which cannot be guaranted.
        // However, if we get the info by this way, we are sure this is the latest user display name
        // See FIXME comment below
        if (user_id in $rootScope.presence) {
            displayName = $rootScope.presence[user_id].content.displayname;
        }

        // FIXME: Would like to use the display name as defined in room members of the room.
        // But this information is the display name of the user when he has joined the room.
        // It does not take into account user display name update
        if (room_id) {
            var room = $rootScope.events.rooms[room_id];
            if (room && (user_id in room.members)) {
                var member = room.members[user_id];
                if (member.content.displayname) {
                    displayName = member.content.displayname;
                }
            }
        }

        if (undefined === displayName) {
            // By default, use the user ID
            displayName = user_id;
        }
        return displayName;
    };
}]);
<commit_msg>fix yet more room id leak disasters<commit_after>/*
 Copyright 2014 OpenMarket Ltd

 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

 http://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.
 */

'use strict';

angular.module('matrixFilter', [])

// Compute the room name according to information we have
.filter('mRoomName', ['$rootScope', 'matrixService', function($rootScope, matrixService) {
    return function(room_id) {
        var roomName;

        // If there is an alias, use it
        // TODO: only one alias is managed for now
        var alias = matrixService.getRoomIdToAliasMapping(room_id);

        var room = $rootScope.events.rooms[room_id];
        if (room) {
            // Get name from room state date
            var room_name_event = room["m.room.name"];
            if (room_name_event) {
                roomName = room_name_event.content.name;
            }
            else if (alias) {
                roomName = alias;
            }
            else if (room.members) {

                var user_id = matrixService.config().user_id;

                // Else, build the name from its users
                // Limit the room renaming to 1:1 room
                if (2 === Object.keys(room.members).length) {
                    for (var i in room.members) {
                        var member = room.members[i];
                        if (member.state_key !== user_id) {

                            if (member.state_key in $rootScope.presence) {
                                // If the user is listed in presence, use the displayname there
                                // as it is the most uptodate
                                // XXX: is this true nowadays?
                                roomName = $rootScope.presence[member.state_key].content.displayname || member.state_key;
                            }
                            else {
                                roomName = member.content.displayname || member.state_key;
                            }
                        }
                    }
                }
                else if (Object.keys(room.members).length <= 1) {

                    var otherUserId;

                    if (Object.keys(room.members)[0] && Object.keys(room.members)[0] !== user_id) {
                        otherUserId = Object.keys(room.members)[0];
                    }
                    else {
                        // it's got to be an invite, or failing that a self-chat;
                        otherUserId = room.inviter || user_id;
/*
                        // XXX: This should all be unnecessary now thanks to using the /rooms/<room>/roomid API

                        // The other member may be in the invite list, get all invited users
                        var invitedUserIDs = [];

                        // XXX: *SURELY* we shouldn't have to trawl through the whole messages list to
                        // find invite - surely the other user should be in room.members with state invited? :/ --Matthew
                        for (var i in room.messages) {
                            var message = room.messages[i];
                            if ("m.room.member" === message.type && "invite" === message.content.membership) {
                                // Filter out the current user
                                var member_id = message.state_key;
                                if (member_id === user_id) {
                                    member_id = message.user_id;
                                }
                                if (member_id !== user_id) {
                                    // Make sure there is no duplicate user
                                    if (-1 === invitedUserIDs.indexOf(member_id)) {
                                        invitedUserIDs.push(member_id);
                                    }
                                }
                            }
                        }

                        // For now, only 1:1 room needs to be renamed. It means only 1 invited user
                        if (1 === invitedUserIDs.length) {
                            otherUserId = invitedUserIDs[0];
                        }
*/
                    }

                    // Try to resolve his displayname in presence global data
                    // XXX: should we be looking in the room state instead, given it should be accurate nowadays?
                    if (otherUserId in $rootScope.presence) {
                        roomName = $rootScope.presence[otherUserId].content.displayname || otherUserId;
                    }
                    else {
                        roomName = otherUserId;
                    }
                }
            }
        }

        // Always show the alias in the room displayed name
        if (roomName && alias && alias !== roomName) {
            roomName += " (" + alias + ")";
        }

        if (undefined === roomName) {
            // By default, use the room ID
            roomName = room_id;

            // XXX: this is *INCREDIBLY* heavy logging for a function that calls every single
            // time any kind of digest runs which refreshes a room name...
            // commenting it out for now.

            // Log some information that lead to this leak
            // console.log("Room ID leak for " + room_id);
            // console.log("room object: " + JSON.stringify(room, undefined, 4));
        }

        return roomName;
    };
}])

// Compute the user display name in a room according to the data already downloaded
.filter('mUserDisplayName', ['$rootScope', function($rootScope) {
    return function(user_id, room_id) {
        var displayName;

        // Try to find the user name among presence data
        // Warning: that means we have received before a presence event for this
        // user which cannot be guaranted.
        // However, if we get the info by this way, we are sure this is the latest user display name
        // See FIXME comment below
        if (user_id in $rootScope.presence) {
            displayName = $rootScope.presence[user_id].content.displayname;
        }

        // FIXME: Would like to use the display name as defined in room members of the room.
        // But this information is the display name of the user when he has joined the room.
        // It does not take into account user display name update
        if (room_id) {
            var room = $rootScope.events.rooms[room_id];
            if (room && (user_id in room.members)) {
                var member = room.members[user_id];
                if (member.content.displayname) {
                    displayName = member.content.displayname;
                }
            }
        }

        if (undefined === displayName) {
            // By default, use the user ID
            displayName = user_id;
        }
        return displayName;
    };
}]);
<|endoftext|>


<commit_before>const { Extra } = require('telegraf');
const axios = require('axios');
const {
  getRandomFavSong, md5, utf8, successfulScrobble,
  canScrobble, error, customError,
} = require('./utils');
const { LASTFM_URL, LASTFM_KEY, LASTFM_SECRET } = require('../../config');
const { findUserById } = require('./dbmanager');
const { proxy } = require('./proxy');

function scrobbleTracks(tracks, timestamp, key) {
  let startTimestamp = (timestamp || Math.floor(Date.now() / 1000)) - tracks
    .map(track => track.duration)
    .reduce((prev, next) => prev + next);
  const names = tracks.map(track => track.name);
  const albums = tracks.map(track => track.album);
  const artists = tracks.map(track => track.artist);
  const timestamps = tracks.map(track => startTimestamp += track.duration);
  const queryAlbums = albums.map((album, i) => `&album[${i}]=${encodeURIComponent(album)}`).sort().join('');
  const queryArtists = artists.map((artist, i) => `&artist[${i}]=${encodeURIComponent(artist)}`).sort().join('');
  const queryTimestamps = timestamps.map((ms, i) => `&timestamp[${i}]=${ms}`).sort().join('');
  const queryTracks = names.map((name, i) => `&track[${i}]=${encodeURIComponent(name)}`).sort().join('');
  const apiSig = md5(utf8(`${queryAlbums}api_key${LASTFM_KEY}${queryArtists}methodtrack.scrobblesk${key}${queryTimestamps}${queryTracks}${LASTFM_SECRET}`.replace(/[&=]/g, '')));

  return axios.post(LASTFM_URL,
    `${queryAlbums.slice(1)}&api_key=${LASTFM_KEY}&api_sig=${apiSig}${queryArtists}&format=json&method=track.scrobble&sk=${key}${queryTimestamps}${queryTracks}`, {
      proxy: proxy.host ? proxy : null,
    });
}

async function scrobbleTrack(ctx, isAlbum = true) {
  const cantScrobbleText = 'You can\'t scrobble tracks more than once in 30 seconds. If you need to scrobble a couple of tracks you can do that via /scrobble command. Please, wait and try again';

  if (ctx.message && ctx.message.text) {
    const track = ctx.message.text.split('\n');
    const song = getRandomFavSong();

    if (track.length < 2 || track.length > 3) {
      return ctx.reply(`Please, send me valid data separated by new lines. Example:\n\n${song.artist}\n${song.name}\n${song.album}\n\nAlbum title is an optional parameter. Type /help for more info`);
    }

    const user = await findUserById(ctx.from.id);

    if (canScrobble(user)) {
      const msg = await ctx.reply('Scrobbling...', Extra.HTML());
      const res = await scrobbleTracks([{
        artist: track[0],
        name: track[1],
        album: track[2] || '',
        duration: 0,
      }], ctx.message.date, user.key);

      if (res.data.scrobbles['@attr'].ingored) {
        return customError(ctx, new Error('❌ Error: Track has been ignored by Last.fm'), msg.message_id);
      }

      return successfulScrobble(ctx, null, msg.message_id);
    }

    return ctx.reply(cantScrobbleText);
  } else if (ctx.callbackQuery) {
    const user = await findUserById(ctx.from.id);

    if (canScrobble(user)) {
      const track = user.track;
      const res = await scrobbleTracks([{
        artist: track.artist,
        name: track.name,
        album: isAlbum ? track.album : '',
        duration: 0,
      }], null, user.key);

      if (res.data.scrobbles['@attr'].ingored) {
        return customError(ctx, new Error('❌ Error: Track has been ignored by Last.fm'));
      }

      return successfulScrobble(ctx);
    }

    return ctx.answerCallbackQuery(cantScrobbleText, undefined, true);
  }

  return error(ctx, new Error(`No scrobbling handler of this type: ${ctx.updateType}`));
}

async function scrobbleAlbum(ctx) {
  const user = await findUserById(ctx.from.id);
  const tracks = user.album.tracks.map(track => ({
    name: track.name,
    artist: user.album.artist,
    album: user.album.title,
    duration: track.duration,
  }));

  await scrobbleTracks(tracks, null, user.key);
  return successfulScrobble(ctx);
}

async function scrobbleTracklist(ctx) {
  let isValid = true;
  let tracks = ctx.message.text.split('\n')
    .map((string) => {
      if (string.split('|').length < 2) {
        isValid = false;
      }

      const track = string.split('|');

      return {
        name: track[1],
        artist: track[0],
        album: track[2] || '',
        duration: 300,
      };
    });
  const parts = [];

  if (!isValid) {
    return ctx.reply('Please, send me valid data with this syntax:\n\nArtist | Track Name | Album Title');
  }

  while (tracks[0]) {
    parts.push(tracks.slice(0, 50));
    tracks = tracks.slice(50);
  }

  const user = await findUserById(ctx.from.id);
  const results = await Promise.all(parts.map(part => scrobbleTracks(part, null, user.key)));
  const ignored = [];

  results.forEach((result) => {
    const scrobbles = result.data.scrobbles.scrobble;

    if (Array.isArray(scrobbles)) {
      scrobbles
        .filter(scrobble => scrobble.ignoredMessage.code === '1')
        .forEach(scr => ignored.push(scr));
    } else {
      if (scrobbles.ignoredMessage.code === '1') ignored.push(scrobbles);
    }
  });

  if (ignored.length) {
    return successfulScrobble(ctx, `Success, but...\nThe following tracks have been ignored:\n\n${ignored.map(track => `${track.artist['#text']} | ${track.track['#text']} | ${track.album['#text']}`).join('\n')}`);
  }

  return successfulScrobble(ctx);
}

module.exports = {
  scrobbleTracks,
  scrobbleTrack,
  scrobbleAlbum,
  scrobbleTracklist,
};
<commit_msg>Add album scrobbling cursive<commit_after>const { Extra } = require('telegraf');
const axios = require('axios');
const {
  getRandomFavSong, md5, utf8, successfulScrobble,
  canScrobble, error, customError,
} = require('./utils');
const { LASTFM_URL, LASTFM_KEY, LASTFM_SECRET } = require('../../config');
const { findUserById } = require('./dbmanager');
const { proxy } = require('./proxy');

function scrobbleTracks(tracks, timestamp, key) {
  let startTimestamp = (timestamp || Math.floor(Date.now() / 1000)) - tracks
    .map(track => track.duration)
    .reduce((prev, next) => prev + next);
  const names = tracks.map(track => track.name);
  const albums = tracks.map(track => track.album);
  const artists = tracks.map(track => track.artist);
  const timestamps = tracks.map(track => startTimestamp += track.duration);
  const queryAlbums = albums.map((album, i) => `&album[${i}]=${encodeURIComponent(album)}`).sort().join('');
  const queryArtists = artists.map((artist, i) => `&artist[${i}]=${encodeURIComponent(artist)}`).sort().join('');
  const queryTimestamps = timestamps.map((ms, i) => `&timestamp[${i}]=${ms}`).sort().join('');
  const queryTracks = names.map((name, i) => `&track[${i}]=${encodeURIComponent(name)}`).sort().join('');
  const apiSig = md5(utf8(`${queryAlbums}api_key${LASTFM_KEY}${queryArtists}methodtrack.scrobblesk${key}${queryTimestamps}${queryTracks}${LASTFM_SECRET}`.replace(/[&=]/g, '')));

  return axios.post(LASTFM_URL,
    `${queryAlbums.slice(1)}&api_key=${LASTFM_KEY}&api_sig=${apiSig}${queryArtists}&format=json&method=track.scrobble&sk=${key}${queryTimestamps}${queryTracks}`, {
      proxy: proxy.host ? proxy : null,
    });
}

async function scrobbleTrack(ctx, isAlbum = true) {
  const cantScrobbleText = 'You can\'t scrobble tracks more than once in 30 seconds. If you need to scrobble a couple of tracks you can do that via /scrobble command. Please, wait and try again';

  if (ctx.message && ctx.message.text) {
    const track = ctx.message.text.split('\n');
    const song = getRandomFavSong();

    if (track.length < 2 || track.length > 3) {
      return ctx.reply(`Please, send me valid data separated by new lines. Example:\n\n${song.artist}\n${song.name}\n${song.album}\n\nAlbum title is an optional parameter. Type /help for more info`);
    }

    const user = await findUserById(ctx.from.id);

    if (canScrobble(user)) {
      const msg = await ctx.reply('Scrobbling...', Extra.HTML());
      const res = await scrobbleTracks([{
        artist: track[0],
        name: track[1],
        album: track[2] || '',
        duration: 0,
      }], ctx.message.date, user.key);

      if (res.data.scrobbles['@attr'].ingored) {
        return customError(ctx, new Error('❌ Error: Track has been ignored by Last.fm'), msg.message_id);
      }

      return successfulScrobble(ctx, null, msg.message_id);
    }

    return ctx.reply(cantScrobbleText);
  } else if (ctx.callbackQuery) {
    const user = await findUserById(ctx.from.id);

    if (canScrobble(user)) {
      const track = user.track;
      const res = await scrobbleTracks([{
        artist: track.artist,
        name: track.name,
        album: isAlbum ? track.album : '',
        duration: 0,
      }], null, user.key);

      if (res.data.scrobbles['@attr'].ingored) {
        return customError(ctx, new Error('❌ Error: Track has been ignored by Last.fm'));
      }

      return successfulScrobble(ctx);
    }

    return ctx.answerCallbackQuery(cantScrobbleText, undefined, true);
  }

  return error(ctx, new Error(`No scrobbling handler of this type: ${ctx.updateType}`));
}

async function scrobbleAlbum(ctx) {
  await ctx.editMessageText('Scrobbling...</>', Extra.HTML());
  const user = await findUserById(ctx.from.id);
  const tracks = user.album.tracks.map(track => ({
    name: track.name,
    artist: user.album.artist,
    album: user.album.title,
    duration: track.duration,
  }));

  await scrobbleTracks(tracks, null, user.key);
  return successfulScrobble(ctx);
}

async function scrobbleTracklist(ctx) {
  let isValid = true;
  let tracks = ctx.message.text.split('\n')
    .map((string) => {
      if (string.split('|').length < 2) {
        isValid = false;
      }

      const track = string.split('|');

      return {
        name: track[1],
        artist: track[0],
        album: track[2] || '',
        duration: 300,
      };
    });
  const parts = [];

  if (!isValid) {
    return ctx.reply('Please, send me valid data with this syntax:\n\nArtist | Track Name | Album Title');
  }

  while (tracks[0]) {
    parts.push(tracks.slice(0, 50));
    tracks = tracks.slice(50);
  }

  const user = await findUserById(ctx.from.id);
  const results = await Promise.all(parts.map(part => scrobbleTracks(part, null, user.key)));
  const ignored = [];

  results.forEach((result) => {
    const scrobbles = result.data.scrobbles.scrobble;

    if (Array.isArray(scrobbles)) {
      scrobbles
        .filter(scrobble => scrobble.ignoredMessage.code === '1')
        .forEach(scr => ignored.push(scr));
    } else {
      if (scrobbles.ignoredMessage.code === '1') ignored.push(scrobbles);
    }
  });

  if (ignored.length) {
    return successfulScrobble(ctx, `Success, but...\nThe following tracks have been ignored:\n\n${ignored.map(track => `${track.artist['#text']} | ${track.track['#text']} | ${track.album['#text']}`).join('\n')}`);
  }

  return successfulScrobble(ctx);
}

module.exports = {
  scrobbleTracks,
  scrobbleTrack,
  scrobbleAlbum,
  scrobbleTracklist,
};
<|endoftext|>


<commit_before>import Ember from 'ember';

export default Ember.Controller.extend({
    preprintNode: null,
    preprint: null,
    nodePreprintsQueried: false,
    usersQueried: false,
    matchingUsers: Ember.A(),
    actions: {
        loadNodeEmbedPreprint() {
            this.store.findAll('node', { include: 'preprints' }).then((nodes) => {
                this.toggleProperty('nodePreprintsQueried');
                for (var node of nodes.toArray()) {
                    let preprints = node.get('preprints').toArray();
                    if (preprints.length > 0 && preprints[0].get('isPublished')) {
                        this.set('preprintNode', node);
                        this.set('preprint', preprints[0]);
                        break;
                    }
                }
            });
        },
        searchUsers(userQuery) {
            this.set('usersQueried', false);
            this.store.query('user', {
                filter: {full_name: userQuery},
                embed: 'nodes'
            }).then((users) => {
                this.set('usersQueried', true);
                this.set('matchingUsers', users);
            });

        }
    }
});
<commit_msg>Style - sorry.<commit_after>import Ember from 'ember';

export default Ember.Controller.extend({
    preprintNode: null,
    preprint: null,
    nodePreprintsQueried: false,
    usersQueried: false,
    matchingUsers: Ember.A(),
    actions: {
        loadNodeEmbedPreprint() {
            this.store.findAll('node', { include: 'preprints' }).then((nodes) => {
                this.toggleProperty('nodePreprintsQueried');
                for (var node of nodes.toArray()) {
                    let preprints = node.get('preprints').toArray();
                    if (preprints.length > 0 && preprints[0].get('isPublished')) {
                        this.set('preprintNode', node);
                        this.set('preprint', preprints[0]);
                        break;
                    }
                }
            });
        },
        searchUsers(userQuery) {
            this.set('usersQueried', false);
            this.store.query('user', {
                filter: { full_name: userQuery },
                embed: 'nodes'
            }).then((users) => {
                this.set('usersQueried', true);
                this.set('matchingUsers', users);
            });

        }
    }
});
<|endoftext|>


<commit_before>var rgbaString = function(color) {
    var alpha = color[3] || 1.0;
    return "rgba(" + Math.round(color[0]) + ", "
                   + Math.round(color[1]) + ", "
                   + Math.round(color[2]) + ", "
                   + alpha + ")";
}

var randcolor = function() {
    return [Math.random()*255,
            Math.random()*255,
            Math.random()*255];
}

var randbound = function(min, max) {
    var range = max - min;
    return (Math.random() * range) + min;
}

// May want to track things using polar coords
// and then translating to x,y
var PiChart = klass.create();
_.extend(PiChart.prototype, {
    initialize: function(pos, rad) {
        this.position = pos;
        this.radius = rad;

        this.rad_begin = 0;
        this.rad_end = Math.PI / 2;

        this.rings = 10;
        this.ringState = [];
        this.maxVarience = Math.PI / 3

        for (var i=0; i<this.rings; i++) {
            this.ringState.push(0);
        }
    },

    tick: function() {
        for (var i=0; i<this.rings; i++) {
            var newVarience = this.ringState[i] + randbound(-0.01, 0.01);
            if (newVarience > this.maxVarience) {
                newVarience = this.maxVarience;
            } else if (newVarience < -this.maxVarience) {
                newVarience = -this.maxVarience;
            }
            this.ringState[i] = newVarience;
        }
    },

    render: function(g) {
        this.tick();

        var ring_width = (this.radius / (this.rings * 2));
        for (var i = 0; i < this.rings; i++) {
            var ring_height = this.radius - (i * ring_width * 2);
            g.beginPath();
            g.fillStyle = rgbaString([127, 0, 127]);
            g.arc(this.position[0], this.position[1], ring_height,
                  this.rad_begin + this.ringState[i],
                  this.rad_end   + this.ringState[i]);
            g.arc(this.position[0], this.position[1], ring_height - ring_width,
                  this.rad_end   + this.ringState[i],
                  this.rad_begin + this.ringState[i],
                  true);
            g.fill();
            g.closePath();
        }
    }
});

var Scene = klass.create();
_.extend(Scene.prototype, {
    initialize: function(canvas) {
        this.canvas = canvas;
        this.width = canvas.width;
        this.height = canvas.height;
        this.graphics = canvas.getContext('2d');
        this.chart = new PiChart([this.width/2, this.height/2], 100);
    },

    render: function() {
        var g = this.graphics;
        g.clearRect(0, 0, this.width, this.height);
        g.save();
        this.chart.render(g);
        g.restore();
    }
});

var Application = klass.create();
_.extend(Application.prototype, {
    initialize: function() {
        this.element = $('#application');
        this.canvas = $('#canvas');
        this.scene = new Scene(this.canvas.get(0));
        this.tasklet = new Tasklet(_.bind(this.onEnterFrame, this), 20);
        this.bindEvents();
    },

    bindEvents: function() {
        this.canvas.on('click', _.bind(this.onCanvasClick, this));
        $(window).on('keypress', _.bind(this.onKeyPress, this));
    },

    run: function() {
        this.tasklet.run();
    },

    onKeyPress: function() {
    },

    onEnterFrame: function() {
        this.scene.render();
    },

    onCanvasClick: function(evt) {
        var scenePos = this.translateScenePosition(evt);
        this.newActor(scenePos);
    }
});

$(document).ready(function () {
    window.application = new Application();
    window.application.run();
});
// vim: set sts=4 sw=4 expandtab:
<commit_msg>demo for rings chart showing multiple factions<commit_after>var rgbaString = function(color) {
    var alpha = color[3] || 1.0;
    return "rgba(" + Math.round(color[0]) + ", "
                   + Math.round(color[1]) + ", "
                   + Math.round(color[2]) + ", "
                   + alpha + ")";
}

var randcolor = function() {
    return [Math.random()*255,
            Math.random()*255,
            Math.random()*255];
}

var randbound = function(min, max) {
    var range = max - min;
    return (Math.random() * range) + min;
}

// May want to track things using polar coords
// and then translating to x,y
var PiChart = klass.create();
_.extend(PiChart.prototype, {
    initialize: function(pos, rad) {
        this.position = pos;
        this.radius = rad;

        this.rad_begin = 0;
        this.rad_end = Math.PI / 2;

        this.factions = [0.25, 0.43, 0.10];
        this.fcolors = [randcolor(), randcolor(), randcolor()];

        this.rings = 10;
        this.ringState = [];
        this.maxVarience = Math.PI / 3

        for (var i=0; i<this.rings; i++) {
            this.ringState.push(randbound(-0.3, 0.3));
        }
    },

    tick: function() {
        for (var i=0; i<this.rings; i++) {
            var newVarience = this.ringState[i] + randbound(-0.01, 0.01);
            if (newVarience > this.maxVarience) {
                newVarience = this.maxVarience;
            } else if (newVarience < -this.maxVarience) {
                newVarience = -this.maxVarience;
            }
            this.ringState[i] = newVarience;
        }
    },

    renderFactionFraction: function(g, r1, r2, rgba) {
        var ring_width = (this.radius / (this.rings * 2));
        for (var i = 0; i < this.rings - 2; i++) {
            var ring_height = this.radius - (i * ring_width * 2);
            g.beginPath();
            g.fillStyle = rgba;
            g.arc(this.position[0], this.position[1], ring_height,
                  r1 + this.ringState[i], r2 + this.ringState[i]);
            g.arc(this.position[0], this.position[1], ring_height - ring_width,
                  r2 + this.ringState[i], r1 + this.ringState[i], true);
            g.fill();
            g.closePath();
        }
    },

    render: function(g) {
        this.tick();

        var last = 0;
        var unclaimed = 1 - _.reduce(this.factions, function (l, r) {
            return l + r;
        }, 0);
        var buffer = (unclaimed * Math.PI * 2) / this.factions.length;
        for (var i=0; i<this.factions.length; i++) {
            var arc = this.factions[i] * Math.PI * 2 + last;
            this.renderFactionFraction(
                    g, last, arc, rgbaString(this.fcolors[i]));
            last = arc + buffer;
        }
    }
});

var Scene = klass.create();
_.extend(Scene.prototype, {
    initialize: function(canvas) {
        this.canvas = canvas;
        this.width = canvas.width;
        this.height = canvas.height;
        this.graphics = canvas.getContext('2d');
        this.chart = new PiChart([this.width/2, this.height/2], 100);
    },

    render: function() {
        var g = this.graphics;
        g.clearRect(0, 0, this.width, this.height);
        g.save();
        this.chart.render(g);
        g.restore();
    }
});

var Application = klass.create();
_.extend(Application.prototype, {
    initialize: function() {
        this.element = $('#application');
        this.canvas = $('#canvas');
        this.scene = new Scene(this.canvas.get(0));
        this.tasklet = new Tasklet(_.bind(this.onEnterFrame, this), 20);
        this.bindEvents();
    },

    bindEvents: function() {
        this.canvas.on('click', _.bind(this.onCanvasClick, this));
        $(window).on('keypress', _.bind(this.onKeyPress, this));
    },

    run: function() {
        this.tasklet.run();
    },

    onKeyPress: function() {
    },

    onEnterFrame: function() {
        this.scene.render();
    },

    onCanvasClick: function(evt) {
        var scenePos = this.translateScenePosition(evt);
        this.newActor(scenePos);
    }
});

$(document).ready(function () {
    window.application = new Application();
    window.application.run();
});
// vim: set sts=4 sw=4 expandtab:
<|endoftext|>


<commit_before>/**
 * toHaveValidAMPForUser matcher.
 *
 * Site Kit by Google, Copyright 2019 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.
 */

/**
 * External dependencies
 */
import jsdom from 'jsdom';
const { JSDOM } = jsdom;

/**
 * WordPress dependencies
 */
import { createURL } from '@wordpress/e2e-test-utils';

/**
 * Internal dependencies
 */
import { fetchPageContent } from '../utils';

/**
 * Matcher to determine if the path has valid AMP for users
 *
 * @param {( string|Object )} path The string URI or page object
 *
 * @return {Object} Matcher results
 */
export async function toHaveValidAMPForUser( path ) {
	let pass, message;
	const urlToFetch = 'object' === typeof path ? path.url() : createURL( path );

	const cookies = await page.cookies();

	// Make sure we have a login cookie
	expect( cookies.some( ( { name } ) => name.match( /^wordpress_logged_in/ ) ) ).toBeTruthy();

	const html = await fetchPageContent( urlToFetch );
	const jsDoc = new JSDOM( html ).window.document;
	try {
		expect( jsDoc.querySelector( '#amp-admin-bar-item-status-icon' ).textContent ).toMatch( '✅' );
		pass = true;
		message = () => 'Expected logged-in user not to have valid AMP';
	} catch ( error ) {
		pass = false;
		message = () => 'Expected logged-in user to have valid AMP';
	}
	return { pass, message };
}
<commit_msg>Throw an error when no cookie is found.<commit_after>/**
 * toHaveValidAMPForUser matcher.
 *
 * Site Kit by Google, Copyright 2019 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.
 */

/**
 * External dependencies
 */
import jsdom from 'jsdom';
const { JSDOM } = jsdom;

/**
 * WordPress dependencies
 */
import { createURL } from '@wordpress/e2e-test-utils';

/**
 * Internal dependencies
 */
import { fetchPageContent } from '../utils';

/**
 * Matcher to determine if the path has valid AMP for users
 *
 * @param {( string|Object )} path The string URI or page object
 *
 * @return {Object} Matcher results
 */
export async function toHaveValidAMPForUser( path ) {
	let pass, message;
	const urlToFetch = 'object' === typeof path ? path.url() : createURL( path );

	const cookies = await page.cookies();

	// Make sure we have a login cookie
	if ( false === cookies.some( ( { name } ) => name.match( /^wordpress_logged_in/ ) ) ) {
		throw new Error( 'toHaveValidAMPForUser failed. User is not logged in.' );
	}

	const html = await fetchPageContent( urlToFetch );
	const jsDoc = new JSDOM( html ).window.document;
	try {
		expect( jsDoc.querySelector( '#amp-admin-bar-item-status-icon' ).textContent ).toMatch( '✅' );
		pass = true;
		message = () => 'Expected logged-in user not to have valid AMP';
	} catch ( error ) {
		pass = false;
		message = () => 'Expected logged-in user to have valid AMP';
	}
	return { pass, message };
}
<|endoftext|>


<commit_before>/**
 * TagManager module setup tests.
 *
 * Site Kit by Google, Copyright 2019 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.
 */

/**
 * WordPress dependencies
 */
import {
	activatePlugin,
	deactivatePlugin,
	visitAdminPage,
	createURL,
} from '@wordpress/e2e-test-utils';

/**
 * Internal dependencies
 */
import {
	deactivateUtilityPlugins,
	resetSiteKit,
	useRequestInterception,
	setSearchConsoleProperty,
	setAMPMode,
	pageWait,
} from '../../../utils';

async function proceedToTagManagerSetup() {
	await visitAdminPage( 'admin.php', 'page=googlesitekit-settings' );
	await page.waitForSelector( '.mdc-tab-bar' );
	await expect( page ).toClick( '.mdc-tab', { text: /connect more services/i } );
	await page.waitForSelector( '.googlesitekit-settings-connect-module--tagmanager' );

	await Promise.all( [
		page.waitForSelector( '.googlesitekit-setup-module__action .mdc-button' ),
		expect( page ).toClick( '.googlesitekit-cta-link', { text: /set up tag manager/i } ),
	] );
}

describe( 'Tag Manager module setup', () => {
	beforeAll( async () => {
		await page.setRequestInterception( true );
		useRequestInterception( ( request ) => {
			if ( request.url().match( 'google-site-kit/v1/data/' ) ) {
				request.respond( {
					status: 200,
				} );
			} else if ( request.url().match( 'modules/tagmanager/data/live-container-version' ) ) {
				// A 404 is returned for containers without a published version.
				request.respond( { status: 404, body: JSON.stringify( { code: 404 } ) } );
			} else if ( request.url().match( /^https:\/\/www\.googletagmanager\.com\/(gtm\.js|amp\.json)/ ) ) {
				request.respond( { status: 200 } );
			} else {
				request.continue();
			}
		} );
	} );

	beforeEach( async () => {
		await activatePlugin( 'e2e-tests-proxy-auth-plugin' );
		await activatePlugin( 'e2e-tests-site-verification-plugin' );
		await activatePlugin( 'e2e-tests-oauth-callback-plugin' );
		await setSearchConsoleProperty();
	} );

	afterEach( async () => {
		await deactivateUtilityPlugins();
		await resetSiteKit();
	} );

	it( 'displays account creation form when user has no Tag Manager account', async () => {
		await activatePlugin( 'e2e-tests-module-setup-tagmanager-api-mock-no-account' );
		await proceedToTagManagerSetup();

		// Intercept the call to window.open and call our API to simulate a created account.
		await page.evaluate( () => {
			window.open = () => {
				window._e2eApiFetch( {
					path: 'google-site-kit/v1/e2e/setup/tagmanager/account-created',
					method: 'post',
				} );
			};
		} );

		// Clicking Create Account button will switch API mock plugins on the server to the one that has accounts.
		await Promise.all( [
			page.waitForResponse( ( res ) => res.url().match( 'google-site-kit/v1/e2e/setup/tagmanager/account-created' ) ),
			expect( page ).toClick( '.mdc-button', { text: /Create an account/i } ),
		] );

		await Promise.all( [
			page.waitForResponse( ( req ) => req.url().match( 'tagmanager/data/accounts' ) ),
			expect( page ).toClick( '.googlesitekit-cta-link', { text: /Re-fetch My Account/i } ),
		] );
		await page.waitForSelector( '.googlesitekit-setup-module__inputs' );

		// Ensure account and container selections are cleared.
		await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-account .mdc-select__selected-text', { text: '' } );
		await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container .mdc-select__selected-text', { text: '' } );

		// Choose an account.
		await expect( page ).toClick( '.googlesitekit-tagmanager__select-account' );
		await expect( page ).toClick( '.mdc-menu-surface--open .mdc-list-item', { text: /test account a/i } );

		// Ensure "Set up a new container" option is present in container select.
		await expect( page ).toClick( '.googlesitekit-tagmanager__select-container' );
		await expect( page ).toMatchElement( '.mdc-menu-surface--open .mdc-list-item', { text: /set up a new container/i } );
		await expect( page ).toClick( '.mdc-menu-surface--open .mdc-list-item', { text: /test container x/i } );

		await pageWait( 1000 );
		await expect( page ).toClick( 'button', { text: /confirm \& continue/i } );

		await page.waitForSelector( '.googlesitekit-publisher-win--win-success' );
		await expect( page ).toMatchElement( '.googlesitekit-publisher-win__title', { text: /Congrats on completing the setup for Tag Manager!/i } );

		// Ensure expected tag is placed.
		await Promise.all( [
			page.goto( createURL( '/' ) ),
			page.waitForNavigation(),
		] );
		await expect( page ).toMatchElement( 'script[src^="https://www.googletagmanager.com/gtm.js?id=GTM-ABCXYZ"]' );
	} );

	it( 'displays available accounts and containers for the chosen account', async () => {
		await activatePlugin( 'e2e-tests-module-setup-tagmanager-api-mock' );
		await proceedToTagManagerSetup();

		// Ensure only web container select is shown.
		await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container--web' );
		await expect( page ).not.toMatchElement( '.googlesitekit-tagmanager__select-container--amp' );

		// Ensure account and container are selected by default.
		await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-account .mdc-select__selected-text', { text: /test account a/i } );
		await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container .mdc-select__selected-text', { text: /test container x/i } );

		// Ensure choosing a different account loads the proper values.
		await expect( page ).toClick( '.googlesitekit-tagmanager__select-account' );
		await Promise.all( [
			page.waitForResponse( ( res ) => res.url().match( 'modules/tagmanager/data' ) ),
			expect( page ).toClick( '.mdc-menu-surface--open .mdc-list-item', { text: /test account b/i } ),
		] );

		// Ensure account is selected.
		await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-account .mdc-select__selected-text', { text: /test account b/i } );

		// Select a container.
		await expect( page ).toClick( '.googlesitekit-tagmanager__select-container' );
		// Ensure no AMP containers are shown as options.
		// expect(...).not.toMatchElement with textContent matching does not work as expected.
		await expect(
			await page.$$eval( '.mdc-menu-surface--open .mdc-list-item', ( nodes ) => !! nodes.find( ( e ) => e.textContent.match( /test amp container/i ) ) )
		).toStrictEqual( false );
		await expect( page ).toClick( '.mdc-menu-surface--open .mdc-list-item', { text: /test container y/i } );

		await pageWait( 1000 );
		await expect( page ).toClick( 'button', { text: /confirm \& continue/i } );

		await page.waitForSelector( '.googlesitekit-publisher-win--win-success' );
		await expect( page ).toMatchElement( '.googlesitekit-publisher-win__title', { text: /Congrats on completing the setup for Tag Manager!/i } );

		// Ensure expected tag is placed.
		await Promise.all( [
			page.goto( createURL( '/' ) ),
			page.waitForNavigation(),
		] );
		await expect( page ).toMatchElement( 'script[src^="https://www.googletagmanager.com/gtm.js?id=GTM-BCDWXY"]' );
	} );

	it( 'displays instructions for account creation when "Set up a new account" option is selected', async () => {
		await activatePlugin( 'e2e-tests-module-setup-tagmanager-api-mock' );
		await proceedToTagManagerSetup();

		// Ensure "setup a new account" is an available choice.
		await expect( page ).toClick( '.googlesitekit-tagmanager__select-account' );
		await expect( page ).toMatchElement( '.mdc-menu-surface--open .mdc-list-item', { text: /set up a new account/i } );

		// Choose set up a new account.
		await expect( page ).toClick( '.mdc-menu-surface--open .mdc-list-item', { text: /set up a new account/i } );

		// Ensure instructions are present.
		await expect( page ).toMatchElement( '.googlesitekit-setup-module p', { text: /to create a new account/i } );

		// Ensure buttons are present.
		await expect( page ).toMatchElement( '.googlesitekit-setup-module .mdc-button', { text: /create an account/i } );
		await expect( page ).toMatchElement( '.googlesitekit-setup-module .googlesitekit-cta-link', { text: /re-fetch my account/i } );
	} );

	describe( 'Setup with AMP active', () => {
		beforeEach( async () => {
			await activatePlugin( 'amp' );
			await activatePlugin( 'e2e-tests-module-setup-tagmanager-api-mock' );
		} );

		afterEach( async () => {
			await deactivatePlugin( 'amp' );
		} );

		describe( 'with Primary AMP', () => {
			beforeEach( async () => {
				await setAMPMode( 'primary' );
				await proceedToTagManagerSetup();
			} );
			it( 'renders only the AMP container select menu', async () => {
				await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container--amp' );
				await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container--amp .mdc-floating-label', { text: 'Container' } );
				await expect( page ).not.toMatchElement( '.googlesitekit-tagmanager__select-container--web' );
			} );
			it( 'validates Homepage AMP for logged-in users', async () => {
				await expect( '/' ).toHaveValidAMPForUser();
			} );
			it( 'validates Homepage AMP for non-logged-in users', async () => {
				await expect( '/' ).toHaveValidAMPForVisitor();
			} );
		} );

		describe( 'with Secondary AMP', () => {
			beforeEach( async () => {
				await setAMPMode( 'secondary' );
				await proceedToTagManagerSetup();
			} );
			it( 'renders both the AMP and web container select menus', async () => {
				await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container--web' );
				await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container--web .mdc-floating-label', { text: 'Web Container' } );
				await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container--amp' );
				await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container--amp .mdc-floating-label', { text: 'AMP Container' } );
			} );
			it( 'validates homepage AMP for logged-in users', async () => {
				await expect( page ).toClick( 'button', { text: /confirm \& continue/i } );
				await page.waitForSelector( '.googlesitekit-publisher-win--win-success' );
				await expect( page ).toMatchElement( '.googlesitekit-publisher-win__title', { text: /Congrats on completing the setup for Tag Manager!/i } );
				await page.goto( createURL( '/', 'amp' ), { waitUntil: 'load' } );
				await expect( page ).toHaveValidAMPForUser();
			} );
			it( 'validates homepage AMP for non-logged-in users', async () => {
				await expect( page ).toClick( 'button', { text: /confirm \& continue/i } );
				await page.waitForSelector( '.googlesitekit-publisher-win--win-success' );
				await expect( page ).toMatchElement( '.googlesitekit-publisher-win__title', { text: /Congrats on completing the setup for Tag Manager!/i } );
				await page.goto( createURL( '/', 'amp' ), { waitUntil: 'load' } );
				await expect( page ).toHaveValidAMPForVisitor();
			} );
		} );
	} );
} );
<commit_msg>Simplify GTM E2E setup with setupSiteKit.<commit_after>/**
 * TagManager module setup tests.
 *
 * Site Kit by Google, Copyright 2019 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.
 */

/**
 * WordPress dependencies
 */
import {
	activatePlugin,
	deactivatePlugin,
	visitAdminPage,
	createURL,
} from '@wordpress/e2e-test-utils';

/**
 * Internal dependencies
 */
import {
	deactivateUtilityPlugins,
	pageWait,
	resetSiteKit,
	setAMPMode,
	setupSiteKit,
	useRequestInterception,
} from '../../../utils';

async function proceedToTagManagerSetup() {
	await visitAdminPage( 'admin.php', 'page=googlesitekit-settings' );
	await page.waitForSelector( '.mdc-tab-bar' );
	await expect( page ).toClick( '.mdc-tab', { text: /connect more services/i } );
	await page.waitForSelector( '.googlesitekit-settings-connect-module--tagmanager' );

	await Promise.all( [
		page.waitForSelector( '.googlesitekit-setup-module__action .mdc-button' ),
		expect( page ).toClick( '.googlesitekit-cta-link', { text: /set up tag manager/i } ),
	] );
}

describe( 'Tag Manager module setup', () => {
	beforeAll( async () => {
		await page.setRequestInterception( true );
		useRequestInterception( ( request ) => {
			if ( request.url().match( 'google-site-kit/v1/data/' ) ) {
				request.respond( {
					status: 200,
				} );
			} else if ( request.url().match( 'modules/tagmanager/data/live-container-version' ) ) {
				// A 404 is returned for containers without a published version.
				request.respond( { status: 404, body: JSON.stringify( { code: 404 } ) } );
			} else if ( request.url().match( /^https:\/\/www\.googletagmanager\.com\/(gtm\.js|amp\.json)/ ) ) {
				request.respond( { status: 200 } );
			} else {
				request.continue();
			}
		} );
	} );

	beforeEach( async () => {
		await setupSiteKit();
	} );

	afterEach( async () => {
		await deactivateUtilityPlugins();
		await resetSiteKit();
	} );

	it( 'displays account creation form when user has no Tag Manager account', async () => {
		await activatePlugin( 'e2e-tests-module-setup-tagmanager-api-mock-no-account' );
		await proceedToTagManagerSetup();

		// Intercept the call to window.open and call our API to simulate a created account.
		await page.evaluate( () => {
			window.open = () => {
				window._e2eApiFetch( {
					path: 'google-site-kit/v1/e2e/setup/tagmanager/account-created',
					method: 'post',
				} );
			};
		} );

		// Clicking Create Account button will switch API mock plugins on the server to the one that has accounts.
		await Promise.all( [
			page.waitForResponse( ( res ) => res.url().match( 'google-site-kit/v1/e2e/setup/tagmanager/account-created' ) ),
			expect( page ).toClick( '.mdc-button', { text: /Create an account/i } ),
		] );

		await Promise.all( [
			page.waitForResponse( ( req ) => req.url().match( 'tagmanager/data/accounts' ) ),
			expect( page ).toClick( '.googlesitekit-cta-link', { text: /Re-fetch My Account/i } ),
		] );
		await page.waitForSelector( '.googlesitekit-setup-module__inputs' );

		// Ensure account and container selections are cleared.
		await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-account .mdc-select__selected-text', { text: '' } );
		await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container .mdc-select__selected-text', { text: '' } );

		// Choose an account.
		await expect( page ).toClick( '.googlesitekit-tagmanager__select-account' );
		await expect( page ).toClick( '.mdc-menu-surface--open .mdc-list-item', { text: /test account a/i } );

		// Ensure "Set up a new container" option is present in container select.
		await expect( page ).toClick( '.googlesitekit-tagmanager__select-container' );
		await expect( page ).toMatchElement( '.mdc-menu-surface--open .mdc-list-item', { text: /set up a new container/i } );
		await expect( page ).toClick( '.mdc-menu-surface--open .mdc-list-item', { text: /test container x/i } );

		await pageWait( 1000 );
		await expect( page ).toClick( 'button', { text: /confirm \& continue/i } );

		await page.waitForSelector( '.googlesitekit-publisher-win--win-success' );
		await expect( page ).toMatchElement( '.googlesitekit-publisher-win__title', { text: /Congrats on completing the setup for Tag Manager!/i } );

		// Ensure expected tag is placed.
		await Promise.all( [
			page.goto( createURL( '/' ) ),
			page.waitForNavigation(),
		] );
		await expect( page ).toMatchElement( 'script[src^="https://www.googletagmanager.com/gtm.js?id=GTM-ABCXYZ"]' );
	} );

	it( 'displays available accounts and containers for the chosen account', async () => {
		await activatePlugin( 'e2e-tests-module-setup-tagmanager-api-mock' );
		await proceedToTagManagerSetup();

		// Ensure only web container select is shown.
		await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container--web' );
		await expect( page ).not.toMatchElement( '.googlesitekit-tagmanager__select-container--amp' );

		// Ensure account and container are selected by default.
		await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-account .mdc-select__selected-text', { text: /test account a/i } );
		await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container .mdc-select__selected-text', { text: /test container x/i } );

		// Ensure choosing a different account loads the proper values.
		await expect( page ).toClick( '.googlesitekit-tagmanager__select-account' );
		await Promise.all( [
			page.waitForResponse( ( res ) => res.url().match( 'modules/tagmanager/data' ) ),
			expect( page ).toClick( '.mdc-menu-surface--open .mdc-list-item', { text: /test account b/i } ),
		] );

		// Ensure account is selected.
		await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-account .mdc-select__selected-text', { text: /test account b/i } );

		// Select a container.
		await expect( page ).toClick( '.googlesitekit-tagmanager__select-container' );
		// Ensure no AMP containers are shown as options.
		// expect(...).not.toMatchElement with textContent matching does not work as expected.
		await expect(
			await page.$$eval( '.mdc-menu-surface--open .mdc-list-item', ( nodes ) => !! nodes.find( ( e ) => e.textContent.match( /test amp container/i ) ) )
		).toStrictEqual( false );
		await expect( page ).toClick( '.mdc-menu-surface--open .mdc-list-item', { text: /test container y/i } );

		await pageWait( 1000 );
		await expect( page ).toClick( 'button', { text: /confirm \& continue/i } );

		await page.waitForSelector( '.googlesitekit-publisher-win--win-success' );
		await expect( page ).toMatchElement( '.googlesitekit-publisher-win__title', { text: /Congrats on completing the setup for Tag Manager!/i } );

		// Ensure expected tag is placed.
		await Promise.all( [
			page.goto( createURL( '/' ) ),
			page.waitForNavigation(),
		] );
		await expect( page ).toMatchElement( 'script[src^="https://www.googletagmanager.com/gtm.js?id=GTM-BCDWXY"]' );
	} );

	it( 'displays instructions for account creation when "Set up a new account" option is selected', async () => {
		await activatePlugin( 'e2e-tests-module-setup-tagmanager-api-mock' );
		await proceedToTagManagerSetup();

		// Ensure "setup a new account" is an available choice.
		await expect( page ).toClick( '.googlesitekit-tagmanager__select-account' );
		await expect( page ).toMatchElement( '.mdc-menu-surface--open .mdc-list-item', { text: /set up a new account/i } );

		// Choose set up a new account.
		await expect( page ).toClick( '.mdc-menu-surface--open .mdc-list-item', { text: /set up a new account/i } );

		// Ensure instructions are present.
		await expect( page ).toMatchElement( '.googlesitekit-setup-module p', { text: /to create a new account/i } );

		// Ensure buttons are present.
		await expect( page ).toMatchElement( '.googlesitekit-setup-module .mdc-button', { text: /create an account/i } );
		await expect( page ).toMatchElement( '.googlesitekit-setup-module .googlesitekit-cta-link', { text: /re-fetch my account/i } );
	} );

	describe( 'Setup with AMP active', () => {
		beforeEach( async () => {
			await activatePlugin( 'amp' );
			await activatePlugin( 'e2e-tests-module-setup-tagmanager-api-mock' );
		} );

		afterEach( async () => {
			await deactivatePlugin( 'amp' );
		} );

		describe( 'with Primary AMP', () => {
			beforeEach( async () => {
				await setAMPMode( 'primary' );
				await proceedToTagManagerSetup();
			} );
			it( 'renders only the AMP container select menu', async () => {
				await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container--amp' );
				await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container--amp .mdc-floating-label', { text: 'Container' } );
				await expect( page ).not.toMatchElement( '.googlesitekit-tagmanager__select-container--web' );
			} );
			it( 'validates Homepage AMP for logged-in users', async () => {
				await expect( '/' ).toHaveValidAMPForUser();
			} );
			it( 'validates Homepage AMP for non-logged-in users', async () => {
				await expect( '/' ).toHaveValidAMPForVisitor();
			} );
		} );

		describe( 'with Secondary AMP', () => {
			beforeEach( async () => {
				await setAMPMode( 'secondary' );
				await proceedToTagManagerSetup();
			} );
			it( 'renders both the AMP and web container select menus', async () => {
				await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container--web' );
				await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container--web .mdc-floating-label', { text: 'Web Container' } );
				await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container--amp' );
				await expect( page ).toMatchElement( '.googlesitekit-tagmanager__select-container--amp .mdc-floating-label', { text: 'AMP Container' } );
			} );
			it( 'validates homepage AMP for logged-in users', async () => {
				await expect( page ).toClick( 'button', { text: /confirm \& continue/i } );
				await page.waitForSelector( '.googlesitekit-publisher-win--win-success' );
				await expect( page ).toMatchElement( '.googlesitekit-publisher-win__title', { text: /Congrats on completing the setup for Tag Manager!/i } );
				await page.goto( createURL( '/', 'amp' ), { waitUntil: 'load' } );
				await expect( page ).toHaveValidAMPForUser();
			} );
			it( 'validates homepage AMP for non-logged-in users', async () => {
				await expect( page ).toClick( 'button', { text: /confirm \& continue/i } );
				await page.waitForSelector( '.googlesitekit-publisher-win--win-success' );
				await expect( page ).toMatchElement( '.googlesitekit-publisher-win__title', { text: /Congrats on completing the setup for Tag Manager!/i } );
				await page.goto( createURL( '/', 'amp' ), { waitUntil: 'load' } );
				await expect( page ).toHaveValidAMPForVisitor();
			} );
		} );
	} );
} );
<|endoftext|>


<commit_before>// Reusable citation popover module
//
// usage: $(citationLink).citationPopover(pub)
//		pub: an object containing the publication data
(function ($) {

	$.fn.citationPopover = function (pub) {

		// Hide popovers when clicking outside, but not when clicking inside
		// from: http://stackoverflow.com/a/14857326
		// LS: this is a bit of a hack, since the default click behavior leaves them open
		// until you click on the button again, but the default focus behavior prevents you
		// from interacting with the content of the popover
		$('body').on('click', function (e) {
		    $('[data-toggle="popover"]').each(function () {
		        //the 'is' for buttons that trigger popups
		        //the 'has' for icons within a button that triggers a popup
		        if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
		            $(this).popover('hide');
		        }
		    });
		});

		$(this).attr("data-content", createCitationText(pub));

		$(this).updateCitationPopover();

		return this;
	}

	// combines and formats the citation metadata for display
	function createCitationText(pub) {
		var text = "
Citation | Bibtex

";
	    // authors
	    text+="
";
		pub.authors.forEach(function(author, index, array) {
			text += author.last_name + ", " + author.first_name.substring(0,1) + ". ";
			if(author.middle_name && author.middle_name.length > 0)
				text += author.middle_name.substring(0,1) + ". ";
			text = text.substring(0, text.length-1) + ", "; // trim last space, add comma
		});
		if(text.length > 0) text = text.substring(0, text.length - 2); // trim last comma and space

		// year
		text += " (" + pub.date.getFullYear() + "). ";

		// title
		text += pub.title + ". ";

		// venue
		text += "" + pub.venue + ". "

		// pages
		if(pub.to_appear) {
			if(pub.num_pages) {
				text += pub.num_pages + " pages.";
			}
			text += " To Appear. ";
		} else if(pub.start_page && pub.end_page) {
			text += pub.start_page + "–" + pub.end_page + ". ";
		}
		if (pub.url !== "None" && pub.url !== "") {
   			text += "DOI: " + pub.url + "";
    	}
	    text+="
";
	    text+="
";
	    text+="@inproceedings{"+pub.authors[0].last_name;
	    if (pub.series && pub.series!="None") {
	    	text+=pub.series.split(" ")[0];
		}
	    text+=",
";
	    text+=" author = {";
	    pub.authors.forEach(function(author, index, array){
			text+=author.last_name+", "+author.first_name;
			if (author.middle_name) {
				text+=" "+author.middle_name;
			}
			if (index != array.length-1){
				text+=" and ";
			}
	    });
	    text+="},
";
	    text+=" title = {"+pub.title+"},
";
	    if(pub.book_title && pub.book_title!="None"){
		text+=" book_title = {"+pub.book_title+"},
";
	    }
	    text+=" book_title_short = {"+pub.venue+"},
";
	    if(pub.series && pub.series!="None"){
		text+=" series = {"+pub.series+"},
"; //Is this what series is?
	    }
	    text+=" year = {"+pub.date.getFullYear()+"},
";
	    if (pub.isbn && pub.isbn!="None" && pub.isbn!="tbd") {
		text+=" isbn = {"+pub.isbn+"},
";
	    }
	    if (pub.geo_location && pub.geo_location!="None") {
		text+=" location = {"+pub.geo_location+"},
";
	    }
	    if (pub.page_num_start && pub.page_num_start!="None") {
		text+=" pages = {"+pub.page_num_start+"--"+pub.page_num_end+"},
";
		text+=" numpages = {"+String(parseInt(pub.page_num_end)-parseInt(pub.page_num_start))+"},
";
	    }
	    if (pub.url && pub.url!="None" && pub.url!="tbd"){
		text+=" doi = {"+pub.url+"},
";
	    }
	    if (pub.acmid && pub.acmid!="None" && pub.acmid!="tbd"){
		text+=" acmid = {"+pub.acmid+"},
";
	    }
	    if (pub.publisher && pub.publisher!="None"){
		text+=" publisher = {"+pub.publisher+"},
";
		text+=" publisher_address = {"+pub.publisher_address+"},
";
	    }
	    if (pub.award && pub.award!="None"){
		text+=" award = {"+pub.award+"},
";
	    }
	    if (pub.total_papers_accepted && pub.total_papers_accepted!="None") {
		text+=" total_papers_submitted = {"+pub.total_papers_submitted+"},
";
		text+=" total_papers_accepted = {"+pub.total_papers_accepted+"},
";
	    }
	    if (pub.video_url && pub.video_url!="None"){
		text+=" video_url = {"+pub.video_url+"},
";
	    }
	    text+=" keywords = {";
	    pub.keywords.forEach(function(keyword, index, array){
		text+=keyword;
		if (index != array.length-1) {
		    text+=", ";
		}
	    });
	    text+="}
}"
	    text+="
";

		return text;
	}

	$.fn.citationclick = function() {
		$("#citation-text").css('display', 'block');
		$("#bibtex-text").css('display', 'none');
	}

	$.fn.bibtexclick = function() {
		$("#bibtex-text").css('display', 'block');
		$("#citation-text").css('display', 'none');
	}

	$.fn.updateCitationPopover = function() {
		$(this).popover({placement: "auto right"})

		// Manual trigger to get around idiosyncrasies of bootstrap's popover control
		$(this).click(function() {

			// prevent multiple popovers from being open at once
			var popovers = $('[data-toggle="popover"]')
			for(var i = 0; i < popovers.length; i++) {
				if(popovers[i] != this) // ignore the current popover
					$(popovers[i]).popover('hide');
			}

			// toggle this popover
			$(this).popover('toggle');
		});
	}

}(jQuery));<commit_msg>fixes big with if statement<commit_after>// Reusable citation popover module
//
// usage: $(citationLink).citationPopover(pub)
//		pub: an object containing the publication data
(function ($) {

	$.fn.citationPopover = function (pub) {

		// Hide popovers when clicking outside, but not when clicking inside
		// from: http://stackoverflow.com/a/14857326
		// LS: this is a bit of a hack, since the default click behavior leaves them open
		// until you click on the button again, but the default focus behavior prevents you
		// from interacting with the content of the popover
		$('body').on('click', function (e) {
		    $('[data-toggle="popover"]').each(function () {
		        //the 'is' for buttons that trigger popups
		        //the 'has' for icons within a button that triggers a popup
		        if (!$(this).is(e.target) && $(this).has(e.target).length === 0 && $('.popover').has(e.target).length === 0) {
		            $(this).popover('hide');
		        }
		    });
		});

		$(this).attr("data-content", createCitationText(pub));

		$(this).updateCitationPopover();

		return this;
	}

	// combines and formats the citation metadata for display
	function createCitationText(pub) {
		var text = "
Citation | Bibtex

";
	    // authors
	    text+="
";
		pub.authors.forEach(function(author, index, array) {
			text += author.last_name + ", " + author.first_name.substring(0,1) + ". ";
			if(author.middle_name && author.middle_name.length > 0)
				text += author.middle_name.substring(0,1) + ". ";
			text = text.substring(0, text.length-1) + ", "; // trim last space, add comma
		});
		if(text.length > 0) text = text.substring(0, text.length - 2); // trim last comma and space

		// year
		text += " (" + pub.date.getFullYear() + "). ";

		// title
		text += pub.title + ". ";

		// venue
		text += "" + pub.venue + ". "

		// pages
		if(pub.to_appear) {
			if(pub.num_pages) {
				text += pub.num_pages + " pages.";
			}
			text += " To Appear. ";
		} else if(pub.start_page && pub.end_page) {
			text += pub.start_page + "–" + pub.end_page + ". ";
		}
		if (pub.url && pub.url!="None" && pub.url!="tbd") {
   			text += "DOI: " + pub.url + "";
    	}
	    text+="
";
	    text+="
";
	    text+="@inproceedings{"+pub.authors[0].last_name;
	    if (pub.series && pub.series!="None") {
	    	text+=pub.series.split(" ")[0];
		}
	    text+=",
";
	    text+=" author = {";
	    pub.authors.forEach(function(author, index, array){
			text+=author.last_name+", "+author.first_name;
			if (author.middle_name) {
				text+=" "+author.middle_name;
			}
			if (index != array.length-1){
				text+=" and ";
			}
	    });
	    text+="},
";
	    text+=" title = {"+pub.title+"},
";
	    if(pub.book_title && pub.book_title!="None"){
		text+=" book_title = {"+pub.book_title+"},
";
	    }
	    text+=" book_title_short = {"+pub.venue+"},
";
	    if(pub.series && pub.series!="None"){
		text+=" series = {"+pub.series+"},
"; //Is this what series is?
	    }
	    text+=" year = {"+pub.date.getFullYear()+"},
";
	    if (pub.isbn && pub.isbn!="None" && pub.isbn!="tbd") {
		text+=" isbn = {"+pub.isbn+"},
";
	    }
	    if (pub.geo_location && pub.geo_location!="None") {
		text+=" location = {"+pub.geo_location+"},
";
	    }
	    if (pub.page_num_start && pub.page_num_start!="None") {
		text+=" pages = {"+pub.page_num_start+"--"+pub.page_num_end+"},
";
		text+=" numpages = {"+String(parseInt(pub.page_num_end)-parseInt(pub.page_num_start))+"},
";
	    }
	    if (pub.url && pub.url!="None" && pub.url!="tbd"){
		text+=" doi = {"+pub.url+"},
";
	    }
	    if (pub.acmid && pub.acmid!="None" && pub.acmid!="tbd"){
		text+=" acmid = {"+pub.acmid+"},
";
	    }
	    if (pub.publisher && pub.publisher!="None"){
		text+=" publisher = {"+pub.publisher+"},
";
		text+=" publisher_address = {"+pub.publisher_address+"},
";
	    }
	    if (pub.award && pub.award!="None"){
		text+=" award = {"+pub.award+"},
";
	    }
	    if (pub.total_papers_accepted && pub.total_papers_accepted!="None") {
		text+=" total_papers_submitted = {"+pub.total_papers_submitted+"},
";
		text+=" total_papers_accepted = {"+pub.total_papers_accepted+"},
";
	    }
	    if (pub.video_url && pub.video_url!="None"){
		text+=" video_url = {"+pub.video_url+"},
";
	    }
	    text+=" keywords = {";
	    pub.keywords.forEach(function(keyword, index, array){
		text+=keyword;
		if (index != array.length-1) {
		    text+=", ";
		}
	    });
	    text+="}
}"
	    text+="
";

		return text;
	}

	$.fn.citationclick = function() {
		$("#citation-text").css('display', 'block');
		$("#bibtex-text").css('display', 'none');
	}

	$.fn.bibtexclick = function() {
		$("#bibtex-text").css('display', 'block');
		$("#citation-text").css('display', 'none');
	}

	$.fn.updateCitationPopover = function() {
		$(this).popover({placement: "auto right"})

		// Manual trigger to get around idiosyncrasies of bootstrap's popover control
		$(this).click(function() {

			// prevent multiple popovers from being open at once
			var popovers = $('[data-toggle="popover"]')
			for(var i = 0; i < popovers.length; i++) {
				if(popovers[i] != this) // ignore the current popover
					$(popovers[i]).popover('hide');
			}

			// toggle this popover
			$(this).popover('toggle');
		});
	}

}(jQuery));<|endoftext|>


<commit_before>let $ = selector => document.querySelector(selector);

function modify({ type, title, year, info }) {
    let object = { title, year, ...info };

    $('#example').setAttribute('type', type);

    $('#movie').removeAttribute('active');
    $('#tv-show').removeAttribute('active');

    $(`#${ type }`).setAttribute('active', true);

    let element;
    for(let key in object)
        if(element = $(`#${ key }`))
            element.innerHTML = object[key] || "";

    $('#body').setAttribute('style', `background-image: url("${ type }.poster.jpg")`);
    $('#poster').setAttribute('src', `${ type }.poster.jpg`);

    let { imdb, tmdb, tvdb } = object,
        ids = { imdb, tmdb, tvdb };

    for(let id in ids)
        $(`#${ id }`).setAttribute('href', (
            ids[id]?
                id == 'imdb'?
                    `https://www.imdb.com/videoembed/${ object[id.toUpperCase()] }/`:
                id == 'tmdb'?
                    `https://www.youtube.com/embed/${ object[id.toUpperCase()] }`:
                `https://www.youtube.com/embed/${ object[id.toUpperCase()] }`:
            'blank.html'
        ));
}

function as(type) {
    open('blank.html', 'frame');

    return modify({
        "movie": {
            'type':  "movie",
            'title': "Being John Malkovich",
            'year':  1999,
            'info': {
                'rating':       "R",
                'runtime':      "1:53",
                'genre':        "Comedy, Drama, Fantasy",
                'release-date': "December 3, 1999 (USA)",

                'imdb': "tt0120601",
                'IMDB': "vi3568894233",
                'tmdb': 492,
                'TMDB': "HdVvjvW_OEo",
                'tvdb': null,
                'TVDB': null,
            },
        },

        "tv-show": {
            'type': "tv-show",
            'title': "Love, Death & Robots",
            'year':  2019,
            'info': {
                'rating':       "TV-MA",
                'runtime':      "0:15",
                'genre':        "Animation, Comedy, Fantasy, Horror, Science-Fiction",
                'release-date': "May 15, 2019 (USA)",

                'imdb': "tt9561862",
                'IMDB': "vi1035648281",
                'tmdb': 86831,
                'TMDB': "wUFwunMKa4E",
                'tvdb': 357888,
                'TVDB': "wUFwunMKa4E",
            },
        },
    }[type]);
}

document.querySelectorAll('#movie, #tv-show').forEach(element => {
    element.onmouseup = event => {
        let self = event.target;

        as(self.id);
    };
});

document.querySelectorAll('[target="frame"]').forEach(element => {
    let body = document.body,
        frame = $('#frame'),
        loading = $('#loading');

    element.onmouseup = event => {
        loading.setAttribute('loading', true);
        loading.removeAttribute('style');
    }

    frame.onload = frame.onerror = event => {
        loading.setAttribute('loading', false);
        setTimeout(() => loading.setAttribute('style', 'display:none'), 500);
    }
});

document.body.onload = event => /#(movie|tv-show)/i.test(location.hash)? as(`${ location.hash.replace('#', '') }`): as('movie');
<commit_msg>Added auto-refresh.<commit_after>let $ = selector => document.querySelector(selector);

function modify({ type, title, year, info }) {
    let object = { title, year, ...info };

    $('#example').setAttribute('type', type);

    $('#movie').removeAttribute('active');
    $('#tv-show').removeAttribute('active');

    $(`#${ type }`).setAttribute('active', true);

    let element;
    for(let key in object)
        if(element = $(`#${ key }`))
            element.innerHTML = object[key] || "";

    $('#body').setAttribute('style', `background-image: url("${ type }.poster.jpg")`);
    $('#poster').setAttribute('src', `${ type }.poster.jpg`);

    let { imdb, tmdb, tvdb } = object,
        ids = { imdb, tmdb, tvdb };

    for(let id in ids)
        $(`#${ id }`).setAttribute('href', (
            ids[id]?
                id == 'imdb'?
                    `https://www.imdb.com/videoembed/${ object[id.toUpperCase()] }/`:
                id == 'tmdb'?
                    `https://www.youtube.com/embed/${ object[id.toUpperCase()] }`:
                `https://www.youtube.com/embed/${ object[id.toUpperCase()] }`:
            'blank.html'
        ));
}

function as(type) {
    open('blank.html', 'frame');

    return modify({
        "movie": {
            'type':  "movie",
            'title': "Being John Malkovich",
            'year':  1999,
            'info': {
                'rating':       "R",
                'runtime':      "1:53",
                'genre':        "Comedy, Drama, Fantasy",
                'release-date': "December 3, 1999 (USA)",

                'imdb': "tt0120601",
                'IMDB': "vi3568894233",
                'tmdb': 492,
                'TMDB': "HdVvjvW_OEo",
                'tvdb': null,
                'TVDB': null,
            },
        },

        "tv-show": {
            'type': "tv-show",
            'title': "Love, Death & Robots",
            'year':  2019,
            'info': {
                'rating':       "TV-MA",
                'runtime':      "0:15",
                'genre':        "Animation, Comedy, Fantasy, Horror, Science-Fiction",
                'release-date': "May 15, 2019 (USA)",

                'imdb': "tt9561862",
                'IMDB': "vi1035648281",
                'tmdb': 86831,
                'TMDB': "wUFwunMKa4E",
                'tvdb': 357888,
                'TVDB': "wUFwunMKa4E",
            },
        },
    }[type]);
}

document.querySelectorAll('#movie, #tv-show').forEach(element => {
    element.onmouseup = event => {
        let self = event.target;

        as(self.id);
    };
});

document.querySelectorAll('[target="frame"]').forEach(element => {
    let body = document.body,
        frame = $('#frame'),
        loading = $('#loading');

    element.onmouseup = event => {
        loading.setAttribute('loading', true);
        loading.removeAttribute('style');
    }

    frame.onload = frame.onerror = event => {
        loading.setAttribute('loading', false);
        setTimeout(() => loading.setAttribute('style', 'display:none'), 500);
    }
});

document.body.onload = async(event) => {
    await(
        /#(movie|tv-show)/i.test(location.hash)?
            as(`${ location.hash.replace('#', '') }`):
        as('movie')
    );

    location.hash = '';
};
<|endoftext|>


<commit_before>import Ember from 'ember';
import selectorFor from '../selector-for';

function cleanWhitespace(jQueryElement) {
  jQueryElement.contents().filter(function() {
    this.innerHTML = $.trim(this.innerText);

    return (this.nodeType === 3 && !/\S/.test(this.nodeValue));
  }).remove();

  return jQueryElement;
}

/* like click() but runs asyncrously allowing you to
use it outside of an andThen function with the same
stuff in the DOM */

export default Ember.Test.registerAsyncHelper('assertTooltipProperties',
  function(app, assert, name, properties = {}) {

    /* Default options */

    const expectedContent = properties.content || 'This is a tooltip';
    const expectedEffectClass = properties.effectClass || 'slide';
    const expectedEvent = properties.event || 'hover';
    const expectedPlace = properties.place || 'top';
    const expectedTypeClass = properties.typeClass || null;
    const typeOfContent = Ember.typeOf(expectedContent);
    const usingComponent = properties.usingComponent || false;

    /* Hover/click to attach the tooltip to the DOM */

    if (expectedEvent === 'click') {
      click(selectorFor(name));
    } else if (expectedEvent === 'hover') {
      mouseOver(name);
    } else if (expectedEvent === 'manual') {
      click(selectorFor(name) + ' + input[type="checkbox"]');
    } else if (expectedEvent === 'focus') {
      triggerEvent(selectorFor(name), 'focus');
    }

    andThen(function() {
      const tooltip = Ember.$('.tooltip')[0];
      const indexOfEffectClass = tooltip.className.indexOf(expectedEffectClass);
      const indexOfTypeClass = tooltip.className.indexOf(expectedTypeClass);
      const style = tooltip.style;

      assert.ok(!!tooltip,
        'The tooltip should be added to the DOM after triggering the show event on the target element');

      /* Check auto - NO TEST */

      /* Check content */

      if (usingComponent) {
        const tooltipHtml = cleanWhitespace($(tooltip))[0];
        const cleanTooltipHtml = tooltipHtml.innerHTML.replace(/id="[^"]*"/g, '').replace(/\s+/, ' ');
        const expectedTargetContent = properties.targetContent || 'Hover over me';
        const target = inspect(name, false);

        assert.equal($.trim(cleanTooltipHtml), expectedContent,
          'The HTML content of the tooltip should be correct');

        console.log();

        assert.equal(target.innerText, expectedTargetContent,
          'The expected target content should contain just the expected text and not the {{tooltip-on-parent}} view');

      } else if (typeOfContent === 'string') {

        assert.equal(tooltip.textContent, expectedContent,
          'The text content of the tooltip should be correct');

      } else if (typeOfContent === 'object') {

        assert.equal(tooltip.innerHTML, expectedContent,
          'The child node of the tooltip should be correct');

      } else {
        Ember.warn('No tooltip content was expected by a call to assertTooltipProperties');
      }

      /* Check effectClass */

      if (expectedEffectClass) {

        assert.ok(indexOfEffectClass > -1,
          'The effect class of the tooltip should be ' + expectedEffectClass);

      } else {

        assert.ok(indexOfEffectClass === -1,
          'The type class of the tooltip should not contain ' + expectedTypeClass);

      }

      /* Check event - NO TEST */

      /* Check place

      We check that place is in the class name, so we know the addon is
      consuming the option */

      assert.ok(tooltip.className.indexOf(expectedPlace) > -1,
        'The placement of the tooltip should be reflected in the tooltip class name');

      /* Check spacing - NO TEST */

      /* Check typeClass */

      if (expectedTypeClass) {

        assert.ok(indexOfTypeClass > -1,
          'The type class of the tooltip should be ' + expectedTypeClass);

      } else {

        assert.ok(indexOfTypeClass === -1,
          'The type class of the tooltip should not contain ' + expectedTypeClass);

      }

    });

    /* Unhover/click so the tooltip is detached from the DOM */

    if (expectedEvent === 'click') {
      click(selectorFor(name));
    } else if (expectedEvent === 'hover') {
      mouseOut(name);
    } else if (expectedEvent === 'manual') {
      click(selectorFor(name) + ' + input[type="checkbox"]');
    }

    /* Then check it has been removed */

    andThen(function() {

      Ember.run.later(this, function() {

        assert.ok(!Ember.$('.tooltip').length,
          'There should not be a tooltip in the DOM after triggering the hide event on the target element');

      }, 200);

    });

  }
);
<commit_msg>In test helper's "close tooltip" section, do nothing for auto-close tests<commit_after>import Ember from 'ember';
import selectorFor from '../selector-for';

function cleanWhitespace(jQueryElement) {
  jQueryElement.contents().filter(function() {
    this.innerHTML = $.trim(this.innerText);

    return (this.nodeType === 3 && !/\S/.test(this.nodeValue));
  }).remove();

  return jQueryElement;
}

/* like click() but runs asyncrously allowing you to
use it outside of an andThen function with the same
stuff in the DOM */

export default Ember.Test.registerAsyncHelper('assertTooltipProperties',
  function(app, assert, name, properties = {}) {

    /* Default options */

    const expectedContent = properties.content || 'This is a tooltip';
    const expectedEffectClass = properties.effectClass || 'slide';
    const expectedEvent = properties.event || 'hover';
    const expectedPlace = properties.place || 'top';
    const expectedTypeClass = properties.typeClass || null;
    const typeOfContent = Ember.typeOf(expectedContent);
    const usingComponent = properties.usingComponent || false;

    /* Hover/click to attach the tooltip to the DOM */

    if (expectedEvent === 'click') {
      click(selectorFor(name));
    } else if (expectedEvent === 'hover') {
      mouseOver(name);
    } else if (expectedEvent === 'manual') {
      click(selectorFor(name) + ' + input[type="checkbox"]');
    } else if (expectedEvent === 'focus') {
      triggerEvent(selectorFor(name), 'focus');
    }

    andThen(function() {
      const tooltip = Ember.$('.tooltip')[0];
      const indexOfEffectClass = tooltip.className.indexOf(expectedEffectClass);
      const indexOfTypeClass = tooltip.className.indexOf(expectedTypeClass);
      const style = tooltip.style;

      assert.ok(!!tooltip,
        'The tooltip should be added to the DOM after triggering the show event on the target element');

      /* Check auto - NO TEST */

      /* Check content */

      if (usingComponent) {
        const tooltipHtml = cleanWhitespace($(tooltip))[0];
        const cleanTooltipHtml = tooltipHtml.innerHTML.replace(/id="[^"]*"/g, '').replace(/\s+/, ' ');
        const expectedTargetContent = properties.targetContent || 'Hover over me';
        const target = inspect(name, false);

        assert.equal($.trim(cleanTooltipHtml), expectedContent,
          'The HTML content of the tooltip should be correct');

        console.log();

        assert.equal(target.innerText, expectedTargetContent,
          'The expected target content should contain just the expected text and not the {{tooltip-on-parent}} view');

      } else if (typeOfContent === 'string') {

        assert.equal(tooltip.textContent, expectedContent,
          'The text content of the tooltip should be correct');

      } else if (typeOfContent === 'object') {

        assert.equal(tooltip.innerHTML, expectedContent,
          'The child node of the tooltip should be correct');

      } else {
        Ember.warn('No tooltip content was expected by a call to assertTooltipProperties');
      }

      /* Check effectClass */

      if (expectedEffectClass) {

        assert.ok(indexOfEffectClass > -1,
          'The effect class of the tooltip should be ' + expectedEffectClass);

      } else {

        assert.ok(indexOfEffectClass === -1,
          'The type class of the tooltip should not contain ' + expectedTypeClass);

      }

      /* Check event - NO TEST */

      /* Check place

      We check that place is in the class name, so we know the addon is
      consuming the option */

      assert.ok(tooltip.className.indexOf(expectedPlace) > -1,
        'The placement of the tooltip should be reflected in the tooltip class name');

      /* Check spacing - NO TEST */

      /* Check typeClass */

      if (expectedTypeClass) {

        assert.ok(indexOfTypeClass > -1,
          'The type class of the tooltip should be ' + expectedTypeClass);

      } else {

        assert.ok(indexOfTypeClass === -1,
          'The type class of the tooltip should not contain ' + expectedTypeClass);

      }

    });

    /* Unhover/click so the tooltip is detached from the DOM */
    if (name.match('auto-close')) {
      // do nothing (just wait for close)
    } else if (expectedEvent === 'click') {
      click(selectorFor(name));
    } else if (expectedEvent === 'hover') {
      mouseOut(name);
    } else if (expectedEvent === 'manual') {
      click(selectorFor(name) + ' + input[type="checkbox"]');
    }

    /* Then check it has been removed */

    andThen(function() {

      Ember.run.later(this, function() {

        assert.ok(!Ember.$('.tooltip').length,
          'There should not be a tooltip in the DOM after triggering the hide event on the target element');

      }, 200);

    });

  }
);
<|endoftext|>


<commit_before>/*---------------------------------------------------------------------------
  Copyright 2013 Microsoft Corporation.

  This is free software; you can redistribute it and/or modify it under the
  terms of the Apache License, Version 2.0. A copy of the License can be
  found in the file "license.txt" at the root of this distribution.
---------------------------------------------------------------------------*/
if (typeof define !== 'function') { var define = require('amdefine')(module) }
define([],function() {

  var Map = (function() {
    function Map() { };

    Map.prototype.clear = function() {
      var self = this;
      self.forEach( function(name,value) {
        self.remove(name);
      });
    }

    Map.prototype.persist = function() {
      return this;
    };

    Map.prototype.copy = function() {
      var self = this;
      var map = new Map();
      self.forEach( function(name,value) {
        map.set(name,value);
      });
      return map;
    }

    Map.prototype.set = function( name, value ) {
      this["/" + name] = value;
    }

    Map.prototype.get = function( name ) {
      return this["/" + name];
    }

    Map.prototype.getOrCreate = function( name, def ) {
      var self = this;
      if (!self.contains(name)) self.set(name,def);
      return self.get(name);
    }

    Map.prototype.contains = function( name ) {
      return (this.get(name) !== undefined);
    }

    Map.prototype.remove = function( name ) {
      delete this["/" + name];
    }

    // apply action to each element. breaks early if action returns "false".
    Map.prototype.forEach = function( action ) {
      var self = this;
      for (var key in self) {
        if (key.substr(0,1) === "/") {
          var res = action(key.substr(1), self[key]);
          if (res===false) return;
        }
      };
    }

    // return a new map where action is applied to every element
    Map.prototype.map = function( action ) {
      var self = this;
      var res = new Map();
      self.forEach( function(name,elem) {
        res.set(name,action(elem));
      });
      return res;
    }

    Map.prototype.elems = function() {
      var self = this;
      var res = [];
      self.forEach( function(name,elem) {
        res.push(elem);
      });
      return res;
    }

    Map.prototype.keyElems = function() {
      var self = this;
      var res = [];
      self.forEach( function(name,elem) {
        res.push( {key:name,value:elem} );
      });
      return res;
    }

    return Map;
  })();

  return Map;
});<commit_msg>Add count function to Map<commit_after>/*---------------------------------------------------------------------------
  Copyright 2013 Microsoft Corporation.

  This is free software; you can redistribute it and/or modify it under the
  terms of the Apache License, Version 2.0. A copy of the License can be
  found in the file "license.txt" at the root of this distribution.
---------------------------------------------------------------------------*/
if (typeof define !== 'function') { var define = require('amdefine')(module) }
define([],function() {

  var Map = (function() {
    function Map() { };

    Map.prototype.count = function() {
      var self = this;
      var total = 0;
      for (var key in self) {
        if (key[0] === "/") {
          total++;
        }
      }
      return total;
    }

    Map.prototype.clear = function() {
      var self = this;
      self.forEach( function(name,value) {
        self.remove(name);
      });
    }

    Map.prototype.persist = function() {
      return this;
    };

    Map.prototype.copy = function() {
      var self = this;
      var map = new Map();
      self.forEach( function(name,value) {
        map.set(name,value);
      });
      return map;
    }

    Map.prototype.set = function( name, value ) {
      this["/" + name] = value;
    }

    Map.prototype.get = function( name ) {
      return this["/" + name];
    }

    Map.prototype.getOrCreate = function( name, def ) {
      var self = this;
      if (!self.contains(name)) self.set(name,def);
      return self.get(name);
    }

    Map.prototype.contains = function( name ) {
      return (this.get(name) !== undefined);
    }

    Map.prototype.remove = function( name ) {
      delete this["/" + name];
    }

    // apply action to each element. breaks early if action returns "false".
    Map.prototype.forEach = function( action ) {
      var self = this;
      for (var key in self) {
        if (key.substr(0,1) === "/") {
          var res = action(key.substr(1), self[key]);
          if (res===false) return;
        }
      };
    }

    // return a new map where action is applied to every element
    Map.prototype.map = function( action ) {
      var self = this;
      var res = new Map();
      self.forEach( function(name,elem) {
        res.set(name,action(name,elem));
      });
      return res;
    }

    Map.prototype.elems = function() {
      var self = this;
      var res = [];
      self.forEach( function(name,elem) {
        res.push(elem);
      });
      return res;
    }

    Map.prototype.keyElems = function() {
      var self = this;
      var res = [];
      self.forEach( function(name,elem) {
        res.push( {key:name,value:elem} );
      });
      return res;
    }

    return Map;
  })();

  return Map;
});<|endoftext|>