<commit_before>//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
//>>description: Popup windows.
//>>label: Popups

define( [ "jquery", "jquery.mobile.widget" ], function( $ ) {
//>>excludeEnd("jqmBuildExclude");
(function($, undefined) {

$.widget("mobile.popup", $.mobile.widget, {
	options: {
		theme: null,
		overlayTheme: null,
		shadow: true,
		corners: true,
		fade: true,
		transition: $.mobile.defaultDialogTransition,
		initSelector: ":jqmData(role='popup')"
	},

	_create: function() {
		var ui = {
		    	screen    : "#ui-popup-screen",
		    	container : "#ui-popup-container"
		    },
		    proto = $(
"
" +
"

" +
"

" +
"
"
),
		    thisPage = this.element.closest(":jqmData(role='page')"),
		    self = this;

                if (thisPage.length === 0)
                    thisPage = $("body");

		// Assign the relevant parts of the proto
		for (var key in ui)
			ui[key] = proto.find(ui[key]).removeAttr("id");

		// Apply the proto
		thisPage.append(ui.screen);
		ui.container.insertAfter(ui.screen);
		ui.container.append(this.element);

		// Define instance variables
		$.extend (this, {
			_ui     : ui,
			_isOpen : false
		});

		$.each (this.options, function(key) {
			// Cause initial options to be applied by their handler by temporarily setting the option to undefined
			// - the handler then sets it to the initial value
			var value = self.options[key];

			self.options[key] = undefined;
			self._setOption(key, value, true);
		});

		ui.screen.bind("vclick", function(e) {
			self.close();
		});
	},

	_realSetTheme: function(dst, theme) {
		var classes = (dst.attr("class") || "").split(" "),
		    alreadyAdded = true,
		    currentTheme = null,
		    matches;

		theme = String(theme);
		while (classes.length > 0) {
			currentTheme = classes.pop();
			matches = currentTheme.match(/^ui-body-([a-z])$/);
			if (matches && matches.length > 1) {
				currentTheme = matches[1];
				break;
			}
			else
				currentTheme = null;
		}

		if (theme !== currentTheme) {
			dst.removeClass("ui-body-" + currentTheme);
			if (theme !== null)
				dst.addClass("ui-body-" + theme);
		}
	},

	_setTheme: function(value) {
		this._realSetTheme(this.element, value);
		this.options.theme = value;
		this.element.attr("data-" + ($.mobile.ns || "") + "theme", value);
	},

	_setOverlayTheme: function(value) {
		this._realSetTheme(this._ui.container, value);
		// The screen must always have some kind of background for fade to work, so, if the theme is being unset,
		// set the background to "a".
		this._realSetTheme(this._ui.screen, (value === "" ? "a" : value));
		this.options.overlayTheme = value;
		this.element.attr("data-" + ($.mobile.ns || "") + "overlay-theme", value);
	},

	_setShadow: function(value) {
		this._ui.container[value ? "addClass" : "removeClass"]("ui-overlay-shadow");
		this.options.shadow = value;
		this.element.attr("data-" + ($.mobile.ns || "") + "shadow", value);
	},

	_setCorners: function(value) {
		this._ui.container[value ? "addClass" : "removeClass"]("ui-corner-all");
		this.options.corners = value;
		this.element.attr("data-" + ($.mobile.ns || "") + "corners", value);
	},

	_setFade: function(value) {
		this.options.fade = value;
		this.element.attr("data-" + ($.mobile.ns || "") + "fade", value);
	},

	_setTransition: function(value) {
		this._ui.container
			.removeClass((this.options.transition || ""))
			.addClass(value);
		this.options.transition = value;
		this.element.attr("data-" + ($.mobile.ns || "") + "transition", value);
	},

	_setOption: function(key, value) {
		var setter = "_set" + key.replace(/^[a-z]/, function(c) {return c.toUpperCase();});

		if (this[setter] !== undefined)
			this[setter](value);
		else
			$.mobile.widget.prototype._setOption.apply(this, arguments);
	},

	_placementCoords: function(x, y) {
		// Try and center the overlay over the given coordinates
		var ret,
		    menuHeight = this._ui.container.outerHeight(true),
		    menuWidth = this._ui.container.outerWidth(true),
		    scrollTop = $(window).scrollTop(),
		    screenHeight = $(window).height(),
		    screenWidth = $(window).width(),
		    halfheight = menuHeight / 2,
		    maxwidth = parseFloat( this._ui.container.css( "max-width" ) ),
		    roomtop = y - scrollTop,
		    roombot = scrollTop + screenHeight - y,
		    newtop, newleft;

		if ( roomtop > menuHeight / 2 && roombot > menuHeight / 2 ) {
		  newtop = y - halfheight;
		}
		else {
		  // 30px tolerance off the edges
		  newtop = roomtop > roombot ? scrollTop + screenHeight - menuHeight - 30 : scrollTop + 30;
		}

		// If the menuwidth is smaller than the screen center is
		if ( menuWidth < maxwidth ) {
		  newleft = ( screenWidth - menuWidth ) / 2;
		}
		else {
		  //otherwise insure a >= 30px offset from the left
		  newleft = x - menuWidth / 2;

		  // 10px tolerance off the edges
		  if ( newleft < 10 ) {
		    newleft = 10;
		  }
		  else
			if ( ( newleft + menuWidth ) > screenWidth ) {
		    newleft = screenWidth - menuWidth - 10;
		  }
		}

		return { x : newleft, y : newtop };
	},

	_bindHashChange: function(){
		var self = this;
		$( window ).one( "hashchange.popup", function(){
			self.close( true );
		});
	},

	_unbindHashChange: function(){
		$( window ).unbind( "hashchange.popup" );
	},

	open: function(x, y) {
		if (!this._isOpen) {
			var self = this,
			    coords = this._placementCoords(
			    	(undefined === x ? window.innerWidth  / 2 : x),
			    	(undefined === y ? window.innerHeight / 2 : y));

			this._ui.screen
				.height($(document).height())
				.removeClass("ui-screen-hidden");

			if (this.options.fade)
				this._ui.screen.animate({opacity: 0.5}, "fast");

			this._ui.container
				.removeClass("ui-selectmenu-hidden")
				.css({
					left: coords.x,
					 top: coords.y
				})
				.addClass("in")
				.animationComplete(function() {
					self._ui.screen.height($(document).height());
				});

			// listen for hashchange that will occur when we set it to null dialog hash
			$( window ).one( "hashchange", function(){
				self._bindHashChange();
			});

			// set hash to non-linkable dialog url
			$.mobile.path.set( "&ui-state=dialog" );

			this._isOpen = true;
		}
	},

	close: function( fromHash ) {
		if (this._isOpen) {
			var self = this,
		    	hideScreen = function() {
		    		self._ui.screen.addClass("ui-screen-hidden");
		    		self._isOpen = false;
		    		self.element.trigger("closed");
		    		self._ui.screen.removeAttr("style");
		    	};

			this._ui.container
				.removeClass("in")
				.addClass("reverse out")
				.animationComplete(function() {
					self._ui.container
						.removeClass("reverse out")
						.addClass("ui-selectmenu-hidden")
						.removeAttr("style");
				});

				if (this.options.fade)
					this._ui.screen.animate({opacity: 0.0}, "fast", hideScreen);
				else
					hideScreen();

				// unbind listener that comes with opening popup
				this._unbindHashChange();

				// if the close event did not come from an internal hash listener, reset URL back
				if( !fromHash ){
					window.history.back();
				}
		}
	}
});

$.mobile.popup.bindPopupToButton = function(btn, popup) {
	if (btn.length === 0 || popup.length === 0) return;

	var btnVClickHandler = function(e) {
	    	// When /this/ button causes a popup, align the popup's theme with that of the button, unless the popup has a theme pre-set
	    	if (!popup.jqmData("overlay-theme-set"))
			popup.popup("option", "overlayTheme", btn.jqmData("theme"))
	    	popup.popup("open",
	    		btn.offset().left + btn.outerWidth()  / 2,
	    		btn.offset().top  + btn.outerHeight() / 2);

            // Swallow event, because it might end up getting picked up by the popup window's screen handler, which
            // will in turn cause the popup window to close - Thanks Sasha!
	    	if (e.stopPropagation)
	    		e.stopPropagation();
	    	if (e.preventDefault)
	    		e.preventDefault();
	    };

	// If the popup has a theme set, prevent it from being clobbered by the associated button
	if ((popup.popup("option", "overlayTheme") || "").match(/[a-z]/))
		popup.jqmData("overlay-theme-set", true);

	btn
		.attr({
			"aria-haspopup": true,
			"aria-owns": btn.attr("href")
		})
		.removeAttr("href")
		.bind("vclick", btnVClickHandler);
}

$(document).bind("pagecreate create", function(e) {
	$($.mobile.popup.prototype.options.initSelector, e.target)
		.not(":jqmData(role='none'), :jqmData(role='nojs')")
		.popup();

	$("a[href^='#']:jqmData(rel='popup')", e.target).each(function() {
		$.mobile.popup.bindPopupToButton($(this), $($(this).attr("href")));

	});
});

})(jQuery);
//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
});
//>>excludeEnd("jqmBuildExclude");
<commit_msg>Coding standards<commit_after>//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
//>>description: Popup windows.
//>>label: Popups

define( [ "jquery", "jquery.mobile.widget" ], function( $ ) {
//>>excludeEnd("jqmBuildExclude");
(function( $, undefined ) {

	$.widget( "mobile.popup", $.mobile.widget, {
		options: {
			theme: null,
			overlayTheme: null,
			shadow: true,
			corners: true,
			fade: true,
			transition: $.mobile.defaultDialogTransition,
			initSelector: ":jqmData(role='popup')"
		},

		_create: function() {
			var ui = {
					screen: "#ui-popup-screen",
					container: "#ui-popup-container"
				},
				proto = $(
					"
" +
					"

" +
					"

" +
					"
"
				),
				thisPage = this.element.closest( ":jqmData(role='page')" ),
				self = this;

			if ( thisPage.length === 0 ) {
				thisPage = $( "body" );
			}

			// Assign the relevant parts of the proto
			for ( var key in ui ) {
				ui[ key ] = proto.find( ui[ key ] ).removeAttr( "id" );
			}

			// Apply the proto
			thisPage.append( ui.screen );
			ui.container.insertAfter( ui.screen );
			ui.container.append( this.element );

			// Define instance variables
			$.extend( this, {
				_ui: ui,
				_isOpen: false
			} );

			$.each( this.options, function( key ) {
				// Cause initial options to be applied by their handler by temporarily setting the option to undefined
				// - the handler then sets it to the initial value
				var value = self.options[ key ];

				self.options[ key ] = undefined;
				self._setOption( key, value, true );
			} );

			ui.screen.bind( "vclick", function( e ) {
				self.close();
			} );
		},

		_realSetTheme: function( dst, theme ) {
			var classes = ( dst.attr( "class" ) || "").split( " " ),
				alreadyAdded = true,
				currentTheme = null,
				matches;

			theme = String( theme );
			while ( classes.length > 0 ) {
				currentTheme = classes.pop();
				matches = currentTheme.match( /^ui-body-([a-z])$/ );
				if ( matches && matches.length > 1 ) {
					currentTheme = matches[ 1 ];
					break;
				} else {
					currentTheme = null;
				}
			}

			if ( theme !== currentTheme ) {
				dst.removeClass( "ui-body-" + currentTheme );
				if ( theme !== null ) {
					dst.addClass( "ui-body-" + theme );
				}
			}
		},

		_setTheme: function( value ) {
			this._realSetTheme( this.element, value );
			this.options.theme = value;
			this.element.attr( "data-" + ( $.mobile.ns || "" ) + "theme", value );
		},

		_setOverlayTheme: function( value ) {
			this._realSetTheme( this._ui.container, value );
			// The screen must always have some kind of background for fade to work, so, if the theme is being unset,
			// set the background to "a".
			this._realSetTheme( this._ui.screen, (value === "" ? "a" : value) );
			this.options.overlayTheme = value;
			this.element.attr( "data-" + ( $.mobile.ns || "" ) + "overlay-theme", value );
		},

		_setShadow: function( value ) {
			this._ui.container[value ? "addClass" : "removeClass"]( "ui-overlay-shadow" );
			this.options.shadow = value;
			this.element.attr( "data-" + ( $.mobile.ns || "" ) + "shadow", value );
		},

		_setCorners: function( value ) {
			this._ui.container[value ? "addClass" : "removeClass"]( "ui-corner-all" );
			this.options.corners = value;
			this.element.attr( "data-" + ( $.mobile.ns || "" ) + "corners", value );
		},

		_setFade: function( value ) {
			this.options.fade = value;
			this.element.attr( "data-" + ( $.mobile.ns || "" ) + "fade", value );
		},

		_setTransition: function( value ) {
			this._ui.container
					.removeClass( this.options.transition || "" )
					.addClass( value );
			this.options.transition = value;
			this.element.attr( "data-" + ( $.mobile.ns || "" ) + "transition", value );
		},

		_setOption: function( key, value ) {
			var setter = "_set" + key.replace( /^[a-z]/, function(c) {
				return c.toUpperCase();
			} );

			if ( this[setter] !== undefined ) {
				this[setter]( value );
			} else {
				$.mobile.widget.prototype._setOption.apply( this, arguments );
			}
		},

		_placementCoords: function( x, y ) {
			// Try and center the overlay over the given coordinates
			var ret,
				menuHeight = this._ui.container.outerHeight( true ),
				menuWidth = this._ui.container.outerWidth( true ),
				scrollTop = $( window ).scrollTop(),
				screenHeight = $( window ).height(),
				screenWidth = $( window ).width(),
				halfheight = menuHeight / 2,
				maxwidth = parseFloat( this._ui.container.css( "max-width" ) ),
				roomtop = y - scrollTop,
				roombot = scrollTop + screenHeight - y,
				newtop, newleft;

			if ( roomtop > menuHeight / 2 && roombot > menuHeight / 2 ) {
				newtop = y - halfheight;
			} else {
				// 30px tolerance off the edges
				newtop = roomtop > roombot ? scrollTop + screenHeight - menuHeight - 30 : scrollTop + 30;
			}

			// If the menuwidth is smaller than the screen center is
			if ( menuWidth < maxwidth ) {
				newleft = ( screenWidth - menuWidth ) / 2;
			} else {
				//otherwise insure a >= 30px offset from the left
				newleft = x - menuWidth / 2;

				// 10px tolerance off the edges
				if ( newleft < 10 ) {
					newleft = 10;
				} else if ( ( newleft + menuWidth ) > screenWidth ) {
					newleft = screenWidth - menuWidth - 10;
				}
			}

			return { x: newleft, y: newtop };
		},

		_bindHashChange: function() {
			var self = this;
			$( window ).one( "hashchange.popup", function() {
				self.close( true );
			} );
		},

		_unbindHashChange: function() {
			$( window ).unbind( "hashchange.popup" );
		},

		open: function( x, y ) {
			if ( !this._isOpen ) {
				var self = this,
					coords = this._placementCoords(
							(undefined === x ? window.innerWidth / 2 : x),
							(undefined === y ? window.innerHeight / 2 : y) );

				this._ui.screen
						.height( $( document ).height() )
						.removeClass( "ui-screen-hidden" );

				if ( this.options.fade ) {
					this._ui.screen.animate( {opacity: 0.5}, "fast" );
				}

				this._ui.container
						.removeClass( "ui-selectmenu-hidden" )
						.css( {
							left: coords.x,
							top: coords.y
						} )
						.addClass( "in" )
						.animationComplete( function() {
							self._ui.screen.height( $( document ).height() );
						} );

				// listen for hashchange that will occur when we set it to null dialog hash
				$( window ).one( "hashchange", function() {
					self._bindHashChange();
				} );

				// set hash to non-linkable dialog url
				$.mobile.path.set( "&ui-state=dialog" );

				this._isOpen = true;
			}
		},

		close: function(fromHash) {
			if ( this._isOpen ) {
				var self = this,
					hideScreen = function() {
						self._ui.screen.addClass( "ui-screen-hidden" );
						self._isOpen = false;
						self.element.trigger( "closed" );
						self._ui.screen.removeAttr( "style" );
					};

				this._ui.container
						.removeClass( "in" )
						.addClass( "reverse out" )
						.animationComplete( function() {
							self._ui.container
									.removeClass( "reverse out" )
									.addClass( "ui-selectmenu-hidden" )
									.removeAttr( "style" );
						} );

				if ( this.options.fade ) {
					this._ui.screen.animate( {opacity: 0.0}, "fast", hideScreen );
				} else {
					hideScreen();
				}

				// unbind listener that comes with opening popup
				this._unbindHashChange();

				// if the close event did not come from an internal hash listener, reset URL back
				if ( !fromHash ) {
					window.history.back();
				}
			}
		}
	} );

	$.mobile.popup.bindPopupToButton = function( btn, popup ) {
		if ( btn.length === 0 || popup.length === 0 ) return;

		var btnVClickHandler = function( e ) {
			// When /this/ button causes a popup, align the popup's theme with that of the button, unless the popup has a theme pre-set
			if ( !popup.jqmData( "overlay-theme-set" ) ) {
				popup.popup( "option", "overlayTheme", btn.jqmData( "theme" ) );
			}
			popup.popup( "open",
					btn.offset().left + btn.outerWidth() / 2,
					btn.offset().top + btn.outerHeight() / 2 );

			// Swallow event, because it might end up getting picked up by the popup window's screen handler, which
			// will in turn cause the popup window to close - Thanks Sasha!
			if ( e.stopPropagation ) {
				e.stopPropagation();
			}
			if ( e.preventDefault ) {
				e.preventDefault();
			}
		};

		// If the popup has a theme set, prevent it from being clobbered by the associated button
		if ( (popup.popup( "option", "overlayTheme" ) || "").match( /[a-z]/ ) ) {
			popup.jqmData( "overlay-theme-set", true );
		}

		btn.attr( {
			"aria-haspopup": true,
			"aria-owns": btn.attr( "href" )
		} )
			.removeAttr( "href" )
			.bind( "vclick", btnVClickHandler );
	};

	$( document ).bind( "pagecreate create", function( e )  {
		$( $.mobile.popup.prototype.options.initSelector, e.target )
				.not( ":jqmData(role='none'), :jqmData(role='nojs')" )
				.popup();

		$( "a[href^='#']:jqmData(rel='popup')", e.target ).each( function() {
			$.mobile.popup.bindPopupToButton( $( this ), $( $( this ).attr( "href" ) ) );
		} );
	} );

})( jQuery );
//>>excludeStart("jqmBuildExclude", pragmas.jqmBuildExclude);
} );
//>>excludeEnd("jqmBuildExclude");
<|endoftext|>


<commit_before>/**
 * Copyright (c) Nicolas Gallagher.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import type { ElementRef } from 'react';
import type { LayoutEvent } from '../types';

import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
import useLayoutEffect from './useLayoutEffect';
import UIManager from '../exports/UIManager';

const DOM_LAYOUT_HANDLER_NAME = '__reactLayoutHandler';

let didWarn = false;
let resizeObserver = null;

function getResizeObserver(): ?ResizeObserver {
  if (canUseDOM && typeof window.ResizeObserver !== 'undefined') {
    if (resizeObserver == null) {
      resizeObserver = new window.ResizeObserver(function(entries) {
        entries.forEach(entry => {
          const node = entry.target;
          const onLayout = node[DOM_LAYOUT_HANDLER_NAME];
          if (typeof onLayout === 'function') {
            // We still need to measure the view because browsers don't yet provide
            // border-box dimensions in the entry
            UIManager.measure(node, (x, y, width, height, left, top) => {
              const event: LayoutEvent = {
                // $FlowFixMe
                nativeEvent: {
                  layout: { x, y, width, height, left, top }
                },
                timeStamp: Date.now()
              };
              Object.defineProperty(event.nativeEvent, 'target', {
                enumerable: true,
                get: () => entry.target
              });
              onLayout(event);
            });
          }
        });
      });
    }
  } else if (!didWarn) {
    if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
      console.warn(
        'onLayout relies on ResizeObserver which is not supported by your browser. ' +
          'Please include a polyfill, e.g., https://github.com/que-etc/resize-observer-polyfill.'
      );
      didWarn = true;
    }
  }
  return resizeObserver;
}

export default function useElementLayout(
  ref: ElementRef<any>,
  onLayout?: (e: LayoutEvent) => void
) {
  const observer = getResizeObserver();

  useLayoutEffect(() => {
    const node = ref.current;
    if (node != null && observer != null && typeof onLayout === 'function') {
      observer.observe(node);
      // $FlowFixMe
      node[DOM_LAYOUT_HANDLER_NAME] = onLayout;
    }
    return () => {
      if (node != null && observer != null) {
        observer.unobserve(node);
        // $FlowFixMe
        delete node[DOM_LAYOUT_HANDLER_NAME];
      }
    };
  }, [ref, onLayout, observer]);
}
<commit_msg>[fix] Avoid needing to memoize onLayout callbacks<commit_after>/**
 * Copyright (c) Nicolas Gallagher.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @flow
 */

import type { ElementRef } from 'react';
import type { LayoutEvent } from '../types';

import { canUseDOM } from 'fbjs/lib/ExecutionEnvironment';
import useLayoutEffect from './useLayoutEffect';
import UIManager from '../exports/UIManager';

const DOM_LAYOUT_HANDLER_NAME = '__reactLayoutHandler';

let didWarn = false;
let resizeObserver = null;

function getResizeObserver(): ?ResizeObserver {
  if (canUseDOM && typeof window.ResizeObserver !== 'undefined') {
    if (resizeObserver == null) {
      resizeObserver = new window.ResizeObserver(function(entries) {
        entries.forEach(entry => {
          const node = entry.target;
          const onLayout = node[DOM_LAYOUT_HANDLER_NAME];
          if (typeof onLayout === 'function') {
            // We still need to measure the view because browsers don't yet provide
            // border-box dimensions in the entry
            UIManager.measure(node, (x, y, width, height, left, top) => {
              const event: LayoutEvent = {
                // $FlowFixMe
                nativeEvent: {
                  layout: { x, y, width, height, left, top }
                },
                timeStamp: Date.now()
              };
              Object.defineProperty(event.nativeEvent, 'target', {
                enumerable: true,
                get: () => entry.target
              });
              onLayout(event);
            });
          }
        });
      });
    }
  } else if (!didWarn) {
    if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') {
      console.warn(
        'onLayout relies on ResizeObserver which is not supported by your browser. ' +
          'Please include a polyfill, e.g., https://github.com/que-etc/resize-observer-polyfill.'
      );
      didWarn = true;
    }
  }
  return resizeObserver;
}

export default function useElementLayout(
  ref: ElementRef<any>,
  onLayout?: ?(e: LayoutEvent) => void
) {
  const observer = getResizeObserver();

  useLayoutEffect(() => {
    const node = ref.current;
    if (node != null) {
      node[DOM_LAYOUT_HANDLER_NAME] = onLayout;
    }
  }, [ref, onLayout]);

  // Observing is done in a separate effect to avoid this effect running
  // when 'onLayout' changes.
  useLayoutEffect(() => {
    const node = ref.current;
    if (node != null && observer != null) {
      if (typeof node[DOM_LAYOUT_HANDLER_NAME] === 'function') {
        observer.observe(node);
      } else {
        observer.unobserve(node);
      }
    }
    return () => {
      if (node != null && observer != null) {
        observer.unobserve(node);
      }
    };
  }, [ref, observer]);
}
<|endoftext|>


<commit_before><commit_msg>Tabs to spaces.<commit_after><|endoftext|>


<commit_before>import Ember from 'ember';
import { module, test } from 'qunit';
import startApp from '../helpers/start-app';

var App;

module('Acceptance: Login', {
  setup: function() {
    if (window.localStorage.getItem('token-testAuth')) {
      window.localStorage.removeItem('token-testAuth');
    }
    App = startApp();
  },

  teardown: function() {
    window.history.pushState('','','/tests');
    Ember.run(App, 'destroy');
  }
});

test('visiting /login when there is not valid login should not redirect', function() {
  visit('/login');

  andThen(function() {
    equal(currentURL(), '/login');
  });
});

test('visiting /login should redirect to index when there is a valid login', function() {
  visit('/login');
  click('button#login');

  andThen(function() {
    equal(currentURL(), '/');
  });
});

test('login fails', function() {
  OAuth2.reopen({
    openWindow: function() {
      return new Ember.RSVP.Promise(function(resolve,reject) {
        reject(new Error('Opening dialog login window failed.'));
      });
    }
  });

  visit('/login');
  click('button#login');

  andThen(function() {
    equal(currentURL(), '/login');
    equal(find('p.error').text(), 'There was an error logging in. Please try again.');
  });
});

test('logging in is successful but current_user returns error', function() {

  window.EmberENV['ember-oauth2'].testAuth.currentUser = window.EmberENV['ember-oauth2'].testAuth.currentUserError;

  visit('/login');
  click('button#login');

  andThen(function() {
    equal(currentURL(), '/login');
    equal(find('p.error').text(), 'There was an error logging in. Please try again.');
  });
});
<commit_msg>pass in assert to test functions<commit_after>import Ember from 'ember';
import { module, test } from 'qunit';
import startApp from '../helpers/start-app';

var App;

module('Acceptance: Login', {
  setup: function() {
    if (window.localStorage.getItem('token-testAuth')) {
      window.localStorage.removeItem('token-testAuth');
    }
    App = startApp();
  },

  teardown: function() {
    window.history.pushState('','','/tests');
    Ember.run(App, 'destroy');
  }
});

test('visiting /login when there is not valid login should not redirect', function(assert) {
  visit('/login');

  andThen(function() {
    assert.equal(currentURL(), '/login');
  });
});

test('visiting /login should redirect to index when there is a valid login', function(assert) {
  visit('/login');
  click('button#login');

  andThen(function() {
    assert.equal(currentURL(), '/');
  });
});

test('login fails', function() {
  OAuth2.reopen({
    openWindow: function() {
      return new Ember.RSVP.Promise(function(resolve,reject) {
        reject(new Error('Opening dialog login window failed.'));
      });
    }
  });

  visit('/login');
  click('button#login');

  andThen(function() {
    equal(currentURL(), '/login');
    equal(find('p.error').text(), 'There was an error logging in. Please try again.');
  });
});

test('logging in is successful but current_user returns error', function() {

  window.EmberENV['ember-oauth2'].testAuth.currentUser = window.EmberENV['ember-oauth2'].testAuth.currentUserError;

  visit('/login');
  click('button#login');

  andThen(function() {
    equal(currentURL(), '/login');
    equal(find('p.error').text(), 'There was an error logging in. Please try again.');
  });
});
<|endoftext|>


<commit_before>/**
* elasticGallery - Simple adaptive gallery.
*
* @author Romazotty <faker152@mail.ru>
* @license Apache 2.0
* @link https://github.com/Romazotty/elasticGallery
* @version 1.0
*/

(function( $ ) {
    var defaults = {
        'row' : '2'
    };

	var methods = {

	    init:function(params) {
	        if (params) {
	        	$.extend(defaults, params);
	        }

	        this.prepend('

');
	        methods.preparation(this);
	        return 	this.each(function(){
	         			$(window).one('load', {item: $(this)}, function(event) {
	         				methods.gentlyShow(methods.resizePicture(event.data.item));
	         			});
	         			$(window).on('resize', {item: $(this)}, function(event) {
	         				methods.resizePicture(event.data.item);
	         			});
	         		});
	    },

	    preparation: function(self){
	    	var containerSide  	= self.width(),
	            elemInRow       = Math.floor(parseFloat(containerSide / 200)) > 4 ? Math.floor(parseFloat(containerSide / 200)) : 4,
	            elemSide       	= parseFloat(containerSide / elemInRow).toFixed(5);

	            console.log(containerSide);

			self.css({
				'height': elemSide * defaults.row
			});

			self.find('a').css({
			    'width': elemSide,
        		'height': elemSide
			});
	    },

	    resizePicture: function(self){
	    	methods.preparation(self);
			self.find('a').each(function(){
		    	var container	= $(this),
		    		picture 	= container.find('img'),
		    		marginLeft 	= 0,
		    		marginTop 	= 0;

		    	if(picture[0].width > picture[0].height){
	                picture.css({'width': 'auto', 'height': '100%'});
	                setTimeout(function(){
	                	marginLeft  = (picture[0].width - container.width()) / 2;
	                	picture.css({'margin-left': '-' + marginLeft + 'px'});
	                }, 150);
	            }

	            if(picture[0].width < picture[0].height){
	                picture.css({'width': '100%', 'height': 'auto'});
	                setTimeout(function(){
		                marginTop   = (picture[0].height - container.height()) / 2;
		                picture.css({'margin-top': '-' + marginTop + 'px'});
	                }, 150);
	            }

	            if(picture[0].width == picture[0].height){
	                picture.css({
	                    'width': '100%',
	                    'height': '100%',
	                    'margin': '0px'
	                });
	            }
	        });

			return self;
	    },

	    gentlyShow: function(self){
	    	self.find('#loader-wrapper').remove();
	    	$.each(self.find('a'), function(i, elem){
			    setTimeout(function(){
			       $(elem).animate({'opacity': '1'}, 2000);
			    },400 + ( i * 100 ));
			});
	    },
	};

	$.fn.elasticGallery = function(method){
	    if ( methods[method] ) {
	        // если запрашиваемый метод существует, мы его вызываем
	        // все параметры, кроме имени метода прийдут в метод
	        // this так же перекочует в метод
	        return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
	    } else if ( typeof method === 'object' || ! method ) {
	        // если первым параметром идет объект, либо совсем пусто
	        // выполняем метод init
	        return methods.init.apply( this, arguments );
	    } else {
	        console.log( 'Метод "' +  method + '" не найден в плагине jQuery.mySimplePlugin' );
	    }
	};
})(jQuery);<commit_msg>update<commit_after>/*!
* elasticGallery - Simple adaptive gallery.
*
* @author Romazotty <faker152@mail.ru>
* @license Apache 2.0
* @link https://github.com/Romazotty/elasticGallery
* @version 1.0
*/

(function( $ ) {
    var defaults = {
        'row' : '2'
    };

	var methods = {

	    init:function(params) {
	        if (params) {
	        	$.extend(defaults, params);
	        }

	        this.prepend('

');
	        methods.preparation(this);
	        return 	this.each(function(){
	         			$(window).one('load', {item: $(this)}, function(event) {
	         				methods.gentlyShow(methods.resizePicture(event.data.item));
	         			});
	         			$(window).on('resize', {item: $(this)}, function(event) {
	         				methods.resizePicture(event.data.item);
	         			});
	         		});
	    },

	    preparation: function(self){
	    	var containerSide  	= self.width(),
	            elemInRow       = Math.floor(parseFloat(containerSide / 200)) > 4 ? Math.floor(parseFloat(containerSide / 200)) : 4,
	            elemSide       	= parseFloat(containerSide / elemInRow).toFixed(5);

	            console.log(containerSide);

			self.css({
				'height': elemSide * defaults.row
			});

			self.find('a').css({
			    'width': elemSide,
        		'height': elemSide
			});
	    },

	    resizePicture: function(self){
	    	methods.preparation(self);
			self.find('a').each(function(){
		    	var container	= $(this),
		    		picture 	= container.find('img'),
		    		marginLeft 	= 0,
		    		marginTop 	= 0;

		    	if(picture[0].width > picture[0].height){
	                picture.css({'width': 'auto', 'height': '100%'});
	                setTimeout(function(){
	                	marginLeft  = (picture[0].width - container.width()) / 2;
	                	picture.css({'margin-left': '-' + marginLeft + 'px'});
	                }, 150);
	            }

	            if(picture[0].width < picture[0].height){
	                picture.css({'width': '100%', 'height': 'auto'});
	                setTimeout(function(){
		                marginTop   = (picture[0].height - container.height()) / 2;
		                picture.css({'margin-top': '-' + marginTop + 'px'});
	                }, 150);
	            }

	            if(picture[0].width == picture[0].height){
	                picture.css({
	                    'width': '100%',
	                    'height': '100%',
	                    'margin': '0px'
	                });
	            }
	        });

			return self;
	    },

	    gentlyShow: function(self){
	    	self.find('#loader-wrapper').remove();
	    	$.each(self.find('a'), function(i, elem){
			    setTimeout(function(){
			       $(elem).animate({'opacity': '1'}, 2000);
			    },400 + ( i * 100 ));
			});
	    },
	};

	$.fn.elasticGallery = function(method){
	    if ( methods[method] ) {
	        // если запрашиваемый метод существует, мы его вызываем
	        // все параметры, кроме имени метода прийдут в метод
	        // this так же перекочует в метод
	        return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
	    } else if ( typeof method === 'object' || ! method ) {
	        // если первым параметром идет объект, либо совсем пусто
	        // выполняем метод init
	        return methods.init.apply( this, arguments );
	    } else {
	        console.log( 'Метод "' +  method + '" не найден в плагине jQuery.mySimplePlugin' );
	    }
	};
})(jQuery);<|endoftext|>


<commit_before>// @flow
/*
 * Screen to scan/show qrcode/text code. Goes into various modes with various options depending on if
 * you're a phone/computer and if you're the existing device or the new device
 */

import Container from '../../forms/container'
import {isLargeScreen, isIOS} from '../../../constants/platform'
import Qr from './qr'
import React, {Component} from 'react'
import {
  Box,
  Button,
  ClickableBox,
  Icon,
  Input,
  NativeStyleSheet,
  TabBar,
  Text,
} from '../../../common-adapters/index.native'
import {TabBarItem} from '../../../common-adapters/tab-bar'
import {
  codePageDeviceRoleExistingPhone,
  codePageModeScanCode,
  codePageModeShowCode,
  codePageModeEnterText,
  codePageModeShowText,
} from '../../../constants/login'
import {globalColors, globalMargins, globalStyles} from '../../../styles'

import type {IconType} from '../../../common-adapters/icon'
import type {Mode} from '../../../constants/types/login'
import type {Props} from '.'

class CodePage extends Component<Props> {
  componentDidMount() {
    this.props.resetQRCodeScanned()
  }

  renderShowCode() {
    return (
      <Qr
        style={{alignItems: 'center', justifyContent: 'center', flexDirection: 'column'}}
        scanning={false}
        onBarCodeRead={() => {}}
        qrCode={this.props.qrCode}
      />
    )
  }

  renderScanCode() {
    if (this.props.qrCodeScanned) {
      // If we are provisioning from existing phone, after scanning we should continue on other device
      const continueOnOtherDevice = this.props.myDeviceRole === codePageDeviceRoleExistingPhone
      const scanMessage = continueOnOtherDevice
        ? 'You should follow the instructions on the other device to continue.'
        : 'Please wait...'
      return (
        <Box
          style={{
            ...stylesQRScan,
            flex: 1,
            flexDirection: 'column',
            alignItems: 'center',
            justifyContent: 'center',
          }}
        >
          <Text type="BodySemibold">Code Scanned</Text>
          {scanMessage && (
            <Text type="Body" style={{textAlign: 'center'}}>
              {scanMessage}
            </Text>
          )}
        </Box>
      )
    }
    return (
      <Qr
        scanning={true}
        onBarCodeRead={code => this.props.qrScanned(code)}
        style={stylesQRScan}
        qrCode={this.props.qrCode}
      >
        {isIOS && (
          <Box style={{alignSelf: 'center', width: 200, height: 200}}>
            <Box style={[stylesScan.box, stylesScan.boxEdge, {left: 0}]} />
            <Box style={[stylesScan.box, stylesScan.boxEdge, {right: 0}]} />
            <Box style={[stylesScan.box, stylesScan.boxCorner, {right: 0, top: 0}]} />
            <Box style={[stylesScan.box, stylesScan.boxCorner, {left: 0, top: 0}]} />
            <Box style={[stylesScan.box, stylesScan.boxCorner, {right: 0, bottom: 0}]} />
            <Box style={[stylesScan.box, stylesScan.boxCorner, {left: 0, bottom: 0}]} />
          </Box>
        )}
      </Qr>
    )
  }

  renderShowText() {
    return (
      <Box style={stylesShowText}>
        <Text type="Terminal" selectable={true} style={stylesTextCode}>
          {this.props.textCode}
        </Text>
      </Box>
    )
  }

  renderEnterText() {
    return (
      <Box style={stylesEnterText}>
        <Icon type="icon-phone-text-code-32" style={{alignSelf: 'center'}} />
        <Input
          errorText={this.props.enterCodeErrorText}
          multiline={true}
          rowsMin={3}
          value={this.props.enterText}
          onChangeText={text => this.props.onChangeText(text)}
        />
        <Button
          type="Primary"
          disabled={!this.props.enterText}
          style={{marginTop: globalMargins.medium}}
          label="Continue"
          onClick={() => this.props.textEntered(codePageModeEnterText)}
        />
      </Box>
    )
  }

  renderSwitchButton(mode: Mode, icon: IconType, label: string) {
    return (
      <ClickableBox
        underlayColor={globalColors.white}
        onClick={() => this.props.setCodePageMode(mode)}
        style={{marginBottom: globalMargins.small, marginTop: globalMargins.small}}
      >
        <Box
          style={{
            ...globalStyles.flexBoxRow,
            alignItems: 'center',
            alignSelf: 'center',
            marginLeft: 10,
            marginRight: 10,
          }}
        >
          <Icon type={icon} />
          <Text type="BodyBigLink" style={{marginLeft: 4}}>
            {label}
          </Text>
        </Box>
      </ClickableBox>
    )
  }

  renderScanCodeForDesktop() {
    return (
      <Container style={stylesContainer} onBack={this.props.onBack}>
        <Box style={stylesIntro}>
          <Text type="Header" style={{marginBottom: 10}}>
            Scan QR code
          </Text>
          <Text type="Body">In the Keybase app on your computer,</Text>
          <Text type="Body">{'go to Devices > Add a new device.'}</Text>
        </Box>
        {this.renderScanCode()}
        {this.renderSwitchButton(codePageModeEnterText, 'icon-phone-text-code-32', 'Type text code instead')}
      </Container>
    )
  }

  renderShowCodeForDesktop() {
    return (
      <Container style={stylesContainer} onBack={this.props.onBack}>
        <Box style={stylesIntro}>
          <Text type="Header" style={{marginBottom: 10}}>
            Scan QR code
          </Text>
          <Text type="Body">{'When adding a new mobile device.'}</Text>
        </Box>
        {this.renderShowCode()}
        {this.renderSwitchButton(codePageModeShowText, 'icon-phone-text-code-32', 'Type text code instead')}
      </Container>
    )
  }

  renderCodeForMobile() {
    return (
      <Container style={stylesContainer} onBack={this.props.onBack}>
        <Box style={stylesIntro}>
          <Text type="Header" style={{marginBottom: 10}}>
            Scan QR code
          </Text>
          <Text type="Body">In the Keybase App on your other phone,</Text>
          <Text type="Body">{'go to Devices > Add a new device.'}</Text>
        </Box>
        <TabBar style={{flex: 1}} underlined={true}>
          <TabBarItem
            selected={this.props.mode === codePageModeShowCode}
            label="Display Code"
            onClick={() => {
              this.props.setCodePageMode(codePageModeShowCode)
            }}
          >
            {this.renderShowCode()}
          </TabBarItem>
          <TabBarItem
            label="Scan Code"
            selected={this.props.mode === codePageModeScanCode}
            onClick={() => {
              this.props.setCodePageMode(codePageModeScanCode)
            }}
          >
            {this.renderScanCode()}
          </TabBarItem>
        </TabBar>
        {this.renderSwitchButton(codePageModeEnterText, 'icon-phone-text-code-32', 'Type text code instead')}
      </Container>
    )
  }

  renderShowTextForDesktop() {
    return (
      <Container style={stylesContainer} onBack={this.props.onBack}>
        <Box style={stylesIntro}>
          <Text type="Header" style={{marginBottom: 10}}>
            Type in text code
          </Text>
          <Text type="Body">Please run</Text>
          <Text type="TerminalInline" backgroundMode="Terminal">
            keybase device add
          </Text>
          <Text type="Body">in the terminal on your computer.</Text>
        </Box>
        {this.renderShowText()}
        {this.renderSwitchButton(codePageModeShowCode, 'icon-phone-qr-code-32', 'Scan QR code instead')}
      </Container>
    )
  }

  renderEnterTextForDesktop() {
    return (
      <Container style={stylesContainer} onBack={this.props.onBack}>
        <Box style={stylesIntro}>
          <Text type="Header" style={{marginBottom: 10}}>
            Type in text code
          </Text>
          <Text type="Body">In the Keybase app on your computer,</Text>
          <Text type="Body">{'go to Devices > Add a new device.'}</Text>
        </Box>
        {this.renderEnterText()}
        {this.renderSwitchButton(codePageModeScanCode, 'icon-phone-qr-code-32', 'Scan QR code instead')}
      </Container>
    )
  }

  renderTextForMobile() {
    return (
      <Container style={stylesContainer} onBack={this.props.onBack}>
        <Box style={stylesIntro}>
          <Text type="Header" style={{marginBottom: 10}}>
            Type text code
          </Text>
          <Text type="Body">In the Keybase App on your other phone,</Text>
          <Text type="Body">{'go to Devices > Add a new device.'}</Text>
        </Box>
        <TabBar underlined={true} style={{flex: 1}}>
          <TabBarItem
            selected={this.props.mode === codePageModeShowText}
            label="Display Code"
            onClick={() => {
              this.props.setCodePageMode(codePageModeShowText)
            }}
          >
            {this.renderShowText()}
          </TabBarItem>
          <TabBarItem
            label="Type Code"
            selected={this.props.mode === codePageModeEnterText}
            onClick={() => {
              this.props.setCodePageMode(codePageModeEnterText)
            }}
          >
            {this.renderEnterText()}
          </TabBarItem>
        </TabBar>
        {this.renderSwitchButton(codePageModeScanCode, 'icon-phone-qr-code-32', 'Scan QR code instead')}
      </Container>
    )
  }

  render() {
    let otherDeviceIsMobile = this.props.otherDeviceRole === codePageDeviceRoleExistingPhone
    switch (this.props.mode) {
      case codePageModeShowCode:
        if (otherDeviceIsMobile) {
          return this.renderCodeForMobile()
        }
        return this.renderShowCodeForDesktop()

      case codePageModeScanCode:
        if (otherDeviceIsMobile) {
          return this.renderCodeForMobile()
        }
        return this.renderScanCodeForDesktop()

      case codePageModeShowText:
        if (otherDeviceIsMobile) {
          return this.renderTextForMobile()
        }
        return this.renderShowTextForDesktop()

      case codePageModeEnterText:
        if (otherDeviceIsMobile) {
          return this.renderTextForMobile()
        }
        return this.renderEnterTextForDesktop()
    }
  }
}

const stylesContainer = {
  ...globalStyles.flexBoxColumn,
  flex: 1,
  alignItems: 'stretch',
  justifyContent: 'flex-start',
}

const stylesIntro = {
  alignItems: 'center',
  marginBottom: globalMargins.tiny,
}

const stylesEnterText = {
  ...globalStyles.flexBoxColumn,
  flex: 1,
  justifyContent: 'center',
  padding: 20,
}

const stylesShowText = {
  ...globalStyles.flexBoxColumn,
  flex: 1,
  justifyContent: 'center',
  marginTop: 10,
  marginBottom: 10,
  padding: 32,
}

const stylesTextCode = {
  ...globalStyles.fontTerminalSemibold,
  paddingTop: globalMargins.tiny,
  fontSize: 19,
  color: globalColors.darkBlue,
  textAlign: 'center',
}

const stylesQRScan = {
  minHeight: isLargeScreen ? 300 : 200,
}

const stylesScan = NativeStyleSheet.create({
  box: {
    backgroundColor: 'red',
    position: 'absolute',
  },
  boxCorner: {
    height: 1,
    width: 20,
  },
  boxEdge: {
    top: 0,
    bottom: 0,
    width: 1,
  },
})

export default CodePage
<commit_msg>Update text for adding a new computer on mobile. (#11698)<commit_after>// @flow
/*
 * Screen to scan/show qrcode/text code. Goes into various modes with various options depending on if
 * you're a phone/computer and if you're the existing device or the new device
 */

import Container from '../../forms/container'
import {isLargeScreen, isIOS} from '../../../constants/platform'
import Qr from './qr'
import React, {Component} from 'react'
import {
  Box,
  Button,
  ClickableBox,
  Icon,
  Input,
  NativeStyleSheet,
  TabBar,
  Text,
} from '../../../common-adapters/index.native'
import {TabBarItem} from '../../../common-adapters/tab-bar'
import {
  codePageDeviceRoleExistingPhone,
  codePageModeScanCode,
  codePageModeShowCode,
  codePageModeEnterText,
  codePageModeShowText,
} from '../../../constants/login'
import {globalColors, globalMargins, globalStyles} from '../../../styles'

import type {IconType} from '../../../common-adapters/icon'
import type {Mode} from '../../../constants/types/login'
import type {Props} from '.'

class CodePage extends Component<Props> {
  _desktopInstructionText() {
    return this.props.myDeviceRole === codePageDeviceRoleExistingPhone
      ? 'Sign into your account in the Keybase app on your computer.'
      : 'In the Keybase app on your computer, go to Devices > Add a new device.'
  }

  componentDidMount() {
    this.props.resetQRCodeScanned()
  }

  renderShowCode() {
    return (
      <Qr
        style={{alignItems: 'center', justifyContent: 'center', flexDirection: 'column'}}
        scanning={false}
        onBarCodeRead={() => {}}
        qrCode={this.props.qrCode}
      />
    )
  }

  renderScanCode() {
    if (this.props.qrCodeScanned) {
      // If we are provisioning from existing phone, after scanning we should continue on other device
      const continueOnOtherDevice = this.props.myDeviceRole === codePageDeviceRoleExistingPhone
      const scanMessage = continueOnOtherDevice
        ? 'You should follow the instructions on the other device to continue.'
        : 'Please wait...'
      return (
        <Box
          style={{
            ...stylesQRScan,
            flex: 1,
            flexDirection: 'column',
            alignItems: 'center',
            justifyContent: 'center',
          }}
        >
          <Text type="BodySemibold">Code Scanned</Text>
          {scanMessage && (
            <Text type="Body" style={{textAlign: 'center'}}>
              {scanMessage}
            </Text>
          )}
        </Box>
      )
    }
    return (
      <Qr
        scanning={true}
        onBarCodeRead={code => this.props.qrScanned(code)}
        style={stylesQRScan}
        qrCode={this.props.qrCode}
      >
        {isIOS && (
          <Box style={{alignSelf: 'center', width: 200, height: 200}}>
            <Box style={[stylesScan.box, stylesScan.boxEdge, {left: 0}]} />
            <Box style={[stylesScan.box, stylesScan.boxEdge, {right: 0}]} />
            <Box style={[stylesScan.box, stylesScan.boxCorner, {right: 0, top: 0}]} />
            <Box style={[stylesScan.box, stylesScan.boxCorner, {left: 0, top: 0}]} />
            <Box style={[stylesScan.box, stylesScan.boxCorner, {right: 0, bottom: 0}]} />
            <Box style={[stylesScan.box, stylesScan.boxCorner, {left: 0, bottom: 0}]} />
          </Box>
        )}
      </Qr>
    )
  }

  renderShowText() {
    return (
      <Box style={stylesShowText}>
        <Text type="Terminal" selectable={true} style={stylesTextCode}>
          {this.props.textCode}
        </Text>
      </Box>
    )
  }

  renderEnterText() {
    return (
      <Box style={stylesEnterText}>
        <Icon type="icon-phone-text-code-32" style={{alignSelf: 'center'}} />
        <Input
          errorText={this.props.enterCodeErrorText}
          multiline={true}
          rowsMin={3}
          value={this.props.enterText}
          onChangeText={text => this.props.onChangeText(text)}
        />
        <Button
          type="Primary"
          disabled={!this.props.enterText}
          style={{marginTop: globalMargins.medium}}
          label="Continue"
          onClick={() => this.props.textEntered(codePageModeEnterText)}
        />
      </Box>
    )
  }

  renderSwitchButton(mode: Mode, icon: IconType, label: string) {
    return (
      <ClickableBox
        underlayColor={globalColors.white}
        onClick={() => this.props.setCodePageMode(mode)}
        style={{marginBottom: globalMargins.small, marginTop: globalMargins.small}}
      >
        <Box
          style={{
            ...globalStyles.flexBoxRow,
            alignItems: 'center',
            alignSelf: 'center',
            marginLeft: 10,
            marginRight: 10,
          }}
        >
          <Icon type={icon} />
          <Text type="BodyBigLink" style={{marginLeft: 4}}>
            {label}
          </Text>
        </Box>
      </ClickableBox>
    )
  }

  renderScanCodeForDesktop() {
    return (
      <Container style={stylesContainer} onBack={this.props.onBack}>
        <Box style={stylesIntro}>
          <Text type="Header" style={{marginBottom: 10}}>
            Scan QR code
          </Text>
          <Text type="Body" style={{textAlign: 'center'}}>
            {this._desktopInstructionText()}
          </Text>
        </Box>
        {this.renderScanCode()}
        {this.renderSwitchButton(codePageModeEnterText, 'icon-phone-text-code-32', 'Type text code instead')}
      </Container>
    )
  }

  renderShowCodeForDesktop() {
    return (
      <Container style={stylesContainer} onBack={this.props.onBack}>
        <Box style={stylesIntro}>
          <Text type="Header" style={{marginBottom: 10}}>
            Scan QR code
          </Text>
          <Text type="Body">{'When adding a new mobile device.'}</Text>
        </Box>
        {this.renderShowCode()}
        {this.renderSwitchButton(codePageModeShowText, 'icon-phone-text-code-32', 'Type text code instead')}
      </Container>
    )
  }

  renderCodeForMobile() {
    return (
      <Container style={stylesContainer} onBack={this.props.onBack}>
        <Box style={stylesIntro}>
          <Text type="Header" style={{marginBottom: 10}}>
            Scan QR code
          </Text>
          <Text type="Body">In the Keybase App on your other phone,</Text>
          <Text type="Body">{'go to Devices > Add a new device.'}</Text>
        </Box>
        <TabBar style={{flex: 1}} underlined={true}>
          <TabBarItem
            selected={this.props.mode === codePageModeShowCode}
            label="Display Code"
            onClick={() => {
              this.props.setCodePageMode(codePageModeShowCode)
            }}
          >
            {this.renderShowCode()}
          </TabBarItem>
          <TabBarItem
            label="Scan Code"
            selected={this.props.mode === codePageModeScanCode}
            onClick={() => {
              this.props.setCodePageMode(codePageModeScanCode)
            }}
          >
            {this.renderScanCode()}
          </TabBarItem>
        </TabBar>
        {this.renderSwitchButton(codePageModeEnterText, 'icon-phone-text-code-32', 'Type text code instead')}
      </Container>
    )
  }

  renderShowTextForDesktop() {
    return (
      <Container style={stylesContainer} onBack={this.props.onBack}>
        <Box style={stylesIntro}>
          <Text type="Header" style={{marginBottom: 10}}>
            Type in text code
          </Text>
          <Text type="Body">Please run</Text>
          <Text type="TerminalInline" backgroundMode="Terminal">
            keybase device add
          </Text>
          <Text type="Body">in the terminal on your computer.</Text>
        </Box>
        {this.renderShowText()}
        {this.renderSwitchButton(codePageModeShowCode, 'icon-phone-qr-code-32', 'Scan QR code instead')}
      </Container>
    )
  }

  renderEnterTextForDesktop() {
    return (
      <Container style={stylesContainer} onBack={this.props.onBack}>
        <Box style={stylesIntro}>
          <Text type="Header" style={{marginBottom: 10}}>
            Type in text code
          </Text>
          <Text type="Body" style={{textAlign: 'center'}}>
            {this._desktopInstructionText()}
          </Text>
        </Box>
        {this.renderEnterText()}
        {this.renderSwitchButton(codePageModeScanCode, 'icon-phone-qr-code-32', 'Scan QR code instead')}
      </Container>
    )
  }

  renderTextForMobile() {
    return (
      <Container style={stylesContainer} onBack={this.props.onBack}>
        <Box style={stylesIntro}>
          <Text type="Header" style={{marginBottom: 10}}>
            Type text code
          </Text>
          <Text type="Body">In the Keybase App on your other phone,</Text>
          <Text type="Body">{'go to Devices > Add a new device.'}</Text>
        </Box>
        <TabBar underlined={true} style={{flex: 1}}>
          <TabBarItem
            selected={this.props.mode === codePageModeShowText}
            label="Display Code"
            onClick={() => {
              this.props.setCodePageMode(codePageModeShowText)
            }}
          >
            {this.renderShowText()}
          </TabBarItem>
          <TabBarItem
            label="Type Code"
            selected={this.props.mode === codePageModeEnterText}
            onClick={() => {
              this.props.setCodePageMode(codePageModeEnterText)
            }}
          >
            {this.renderEnterText()}
          </TabBarItem>
        </TabBar>
        {this.renderSwitchButton(codePageModeScanCode, 'icon-phone-qr-code-32', 'Scan QR code instead')}
      </Container>
    )
  }

  render() {
    let otherDeviceIsMobile = this.props.otherDeviceRole === codePageDeviceRoleExistingPhone
    switch (this.props.mode) {
      case codePageModeShowCode:
        if (otherDeviceIsMobile) {
          return this.renderCodeForMobile()
        }
        return this.renderShowCodeForDesktop()

      case codePageModeScanCode:
        if (otherDeviceIsMobile) {
          return this.renderCodeForMobile()
        }
        return this.renderScanCodeForDesktop()

      case codePageModeShowText:
        if (otherDeviceIsMobile) {
          return this.renderTextForMobile()
        }
        return this.renderShowTextForDesktop()

      case codePageModeEnterText:
        if (otherDeviceIsMobile) {
          return this.renderTextForMobile()
        }
        return this.renderEnterTextForDesktop()
    }
  }
}

const stylesContainer = {
  ...globalStyles.flexBoxColumn,
  flex: 1,
  alignItems: 'stretch',
  justifyContent: 'flex-start',
}

const stylesIntro = {
  alignItems: 'center',
  marginBottom: globalMargins.tiny,
}

const stylesEnterText = {
  ...globalStyles.flexBoxColumn,
  flex: 1,
  justifyContent: 'center',
  padding: 20,
}

const stylesShowText = {
  ...globalStyles.flexBoxColumn,
  flex: 1,
  justifyContent: 'center',
  marginTop: 10,
  marginBottom: 10,
  padding: 32,
}

const stylesTextCode = {
  ...globalStyles.fontTerminalSemibold,
  paddingTop: globalMargins.tiny,
  fontSize: 19,
  color: globalColors.darkBlue,
  textAlign: 'center',
}

const stylesQRScan = {
  minHeight: isLargeScreen ? 300 : 200,
}

const stylesScan = NativeStyleSheet.create({
  box: {
    backgroundColor: 'red',
    position: 'absolute',
  },
  boxCorner: {
    height: 1,
    width: 20,
  },
  boxEdge: {
    top: 0,
    bottom: 0,
    width: 1,
  },
})

export default CodePage
<|endoftext|>


<commit_before>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow
 */

'use strict';

const RNTesterExampleFilter = require('./RNTesterExampleFilter');
const RNTesterComponentTitle = require('./RNTesterComponentTitle');
const React = require('react');

const {
  Platform,
  SectionList,
  StyleSheet,
  Text,
  TouchableHighlight,
  Image,
  View,
} = require('react-native');

import {RNTesterThemeContext} from './RNTesterTheme';

const ExampleCard = ({
  onShowUnderlay,
  onHideUnderlay,
  item,
  toggleBookmark,
  handlePress,
}) => {
  const theme = React.useContext(RNTesterThemeContext);
  const platform = item.module.platform;
  const onIos = !platform || platform === 'ios';
  const onAndroid = !platform || platform === 'android';
  return (
    <TouchableHighlight
      testID={item.module.title}
      onShowUnderlay={onShowUnderlay}
      onHideUnderlay={onHideUnderlay}
      accessibilityLabel={item.module.title + ' ' + item.module.description}
      style={styles.listItem}
      underlayColor={'rgb(242,242,242)'}
      onPress={() =>
        handlePress({exampleType: item.exampleType, key: item.key})
      }>
      <View
        style={[styles.row, {backgroundColor: theme.SystemBackgroundColor}]}>
        <View style={styles.topRowStyle}>
          <RNTesterComponentTitle>{item.module.title}</RNTesterComponentTitle>
          <TouchableHighlight
            style={styles.imageViewStyle}
            onPress={() =>
              toggleBookmark({exampleType: item.exampleType, key: item.key})
            }>
            <Image
              style={styles.imageStyle}
              source={
                item.isBookmarked
                  ? require('../assets/bookmark-outline-blue.png')
                  : require('../assets/bookmark-outline-gray.png')
              }
            />
          </TouchableHighlight>
        </View>
        <Text
          style={[
            styles.rowDetailText,
            {color: theme.SecondaryLabelColor, marginBottom: 5},
          ]}>
          {item.module.description}
        </Text>
        <View style={styles.bottomRowStyle}>
          <Text style={{color: theme.SecondaryLabelColor, width: 65}}>
            {item.module.category || 'Other'}
          </Text>
          <View style={styles.platformLabelStyle}>
            <Text
              style={{
                color: onIos ? '#787878' : theme.SeparatorColor,
                fontWeight: onIos ? '500' : '300',
              }}>
              iOS
            </Text>
            <Text
              style={{
                color: onAndroid ? '#787878' : theme.SeparatorColor,
                fontWeight: onAndroid ? '500' : '300',
              }}>
              Android
            </Text>
          </View>
        </View>
      </View>
    </TouchableHighlight>
  );
};

const renderSectionHeader = ({section}) => (
  <RNTesterThemeContext.Consumer>
    {theme => {
      return (
        <Text
          style={[
            styles.sectionHeader,
            {
              color: theme.SecondaryLabelColor,
              backgroundColor: theme.GroupedBackgroundColor,
            },
          ]}>
          {section.title}
        </Text>
      );
    }}
  </RNTesterThemeContext.Consumer>
);

const RNTesterExampleList: React$AbstractComponent<any, void> = React.memo(
  ({sections, toggleBookmark, handleExampleCardPress}) => {
    const theme = React.useContext(RNTesterThemeContext);

    const filter = ({example, filterRegex, category}) =>
      filterRegex.test(example.module.title) &&
      (!category || example.category === category) &&
      (!Platform.isTV || example.supportsTVOS);

    const renderListItem = ({item, section, separators}) => {
      return (
        <ExampleCard
          item={item}
          section={section}
          onShowUnderlay={separators.highlight}
          onHideUnderlay={separators.unhighlight}
          toggleBookmark={toggleBookmark}
          handlePress={handleExampleCardPress}
        />
      );
    };

    return (
      <View
        style={[
          styles.listContainer,
          {backgroundColor: theme.SecondaryGroupedBackgroundColor},
        ]}>
        <RNTesterExampleFilter
          testID="explorer_search"
          page="components_page"
          sections={sections}
          filter={filter}
          render={({filteredSections}) => (
            <SectionList
              sections={filteredSections}
              extraData={filteredSections}
              renderItem={renderListItem}
              ItemSeparatorComponent={ItemSeparator}
              keyboardShouldPersistTaps="handled"
              automaticallyAdjustContentInsets={false}
              keyboardDismissMode="on-drag"
              renderSectionHeader={renderSectionHeader}
              ListFooterComponent={() => <View style={{height: 80}} />}
            />
          )}
        />
      </View>
    );
  },
);

const ItemSeparator = ({highlighted}) => (
  <RNTesterThemeContext.Consumer>
    {theme => {
      return (
        <View
          style={
            highlighted
              ? [
                  styles.separatorHighlighted,
                  {backgroundColor: theme.OpaqueSeparatorColor},
                ]
              : [styles.separator, {backgroundColor: theme.SeparatorColor}]
          }
        />
      );
    }}
  </RNTesterThemeContext.Consumer>
);

const styles = StyleSheet.create({
  listContainer: {
    flex: 1,
  },
  listItem: {
    backgroundColor: Platform.select({ios: '#FFFFFF', android: '#F3F8FF'}),
  },
  sectionHeader: {
    padding: 5,
    fontWeight: '500',
    fontSize: 11,
  },
  row: {
    justifyContent: 'center',
    paddingHorizontal: 15,
    paddingVertical: 12,
    marginVertical: Platform.select({ios: 4, android: 8}),
    marginHorizontal: 15,
    overflow: 'hidden',
    elevation: 5,
  },
  separator: {
    height: Platform.select({ios: StyleSheet.hairlineWidth, android: 0}),
    marginHorizontal: Platform.select({ios: 15, android: 0}),
  },
  separatorHighlighted: {
    height: StyleSheet.hairlineWidth,
  },
  topRowStyle: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    flex: 1,
  },
  bottomRowStyle: {
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  rowDetailText: {
    fontSize: 12,
    lineHeight: 20,
  },
  imageViewStyle: {
    height: 30,
    width: 30,
    borderRadius: 15,
    justifyContent: 'center',
    alignItems: 'center',
    position: 'relative',
    bottom: 5,
  },
  imageStyle: {
    height: 25,
    width: 25,
  },
  platformLabelStyle: {
    flexDirection: 'row',
    width: 100,
    justifyContent: 'space-between',
  },
});

module.exports = RNTesterExampleList;
<commit_msg>Remove filter pills from example page (#30198)<commit_after>/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow
 */

'use strict';

const RNTesterExampleFilter = require('./RNTesterExampleFilter');
const RNTesterComponentTitle = require('./RNTesterComponentTitle');
const React = require('react');

const {
  Platform,
  SectionList,
  StyleSheet,
  Text,
  TouchableHighlight,
  Image,
  View,
} = require('react-native');

import {RNTesterThemeContext} from './RNTesterTheme';

const ExampleCard = ({
  onShowUnderlay,
  onHideUnderlay,
  item,
  toggleBookmark,
  handlePress,
}) => {
  const theme = React.useContext(RNTesterThemeContext);
  const platform = item.module.platform;
  const onIos = !platform || platform === 'ios';
  const onAndroid = !platform || platform === 'android';
  return (
    <TouchableHighlight
      testID={item.module.title}
      onShowUnderlay={onShowUnderlay}
      onHideUnderlay={onHideUnderlay}
      accessibilityLabel={item.module.title + ' ' + item.module.description}
      style={styles.listItem}
      underlayColor={'rgb(242,242,242)'}
      onPress={() =>
        handlePress({exampleType: item.exampleType, key: item.key})
      }>
      <View
        style={[styles.row, {backgroundColor: theme.SystemBackgroundColor}]}>
        <View style={styles.topRowStyle}>
          <RNTesterComponentTitle>{item.module.title}</RNTesterComponentTitle>
          <TouchableHighlight
            style={styles.imageViewStyle}
            onPress={() =>
              toggleBookmark({exampleType: item.exampleType, key: item.key})
            }>
            <Image
              style={styles.imageStyle}
              source={
                item.isBookmarked
                  ? require('../assets/bookmark-outline-blue.png')
                  : require('../assets/bookmark-outline-gray.png')
              }
            />
          </TouchableHighlight>
        </View>
        <Text
          style={[
            styles.rowDetailText,
            {color: theme.SecondaryLabelColor, marginBottom: 5},
          ]}>
          {item.module.description}
        </Text>
        <View style={styles.bottomRowStyle}>
          <Text style={{color: theme.SecondaryLabelColor, width: 65}}>
            {item.module.category || 'Other'}
          </Text>
          <View style={styles.platformLabelStyle}>
            <Text
              style={{
                color: onIos ? '#787878' : theme.SeparatorColor,
                fontWeight: onIos ? '500' : '300',
              }}>
              iOS
            </Text>
            <Text
              style={{
                color: onAndroid ? '#787878' : theme.SeparatorColor,
                fontWeight: onAndroid ? '500' : '300',
              }}>
              Android
            </Text>
          </View>
        </View>
      </View>
    </TouchableHighlight>
  );
};

const renderSectionHeader = ({section}) => (
  <RNTesterThemeContext.Consumer>
    {theme => {
      return (
        <Text
          style={[
            styles.sectionHeader,
            {
              color: theme.SecondaryLabelColor,
              backgroundColor: theme.GroupedBackgroundColor,
            },
          ]}>
          {section.title}
        </Text>
      );
    }}
  </RNTesterThemeContext.Consumer>
);

const RNTesterExampleList: React$AbstractComponent<any, void> = React.memo(
  ({sections, toggleBookmark, handleExampleCardPress}) => {
    const theme = React.useContext(RNTesterThemeContext);

    const filter = ({example, filterRegex, category}) =>
      filterRegex.test(example.module.title) &&
      (!category || example.category === category) &&
      (!Platform.isTV || example.supportsTVOS);

    const renderListItem = ({item, section, separators}) => {
      return (
        <ExampleCard
          item={item}
          section={section}
          onShowUnderlay={separators.highlight}
          onHideUnderlay={separators.unhighlight}
          toggleBookmark={toggleBookmark}
          handlePress={handleExampleCardPress}
        />
      );
    };

    return (
      <View
        style={[
          styles.listContainer,
          {backgroundColor: theme.SecondaryGroupedBackgroundColor},
        ]}>
        <RNTesterExampleFilter
          testID="explorer_search"
          page="components_page"
          sections={sections}
          filter={filter}
          hideFilterPills={true}
          render={({filteredSections}) => (
            <SectionList
              sections={filteredSections}
              extraData={filteredSections}
              renderItem={renderListItem}
              ItemSeparatorComponent={ItemSeparator}
              keyboardShouldPersistTaps="handled"
              automaticallyAdjustContentInsets={false}
              keyboardDismissMode="on-drag"
              renderSectionHeader={renderSectionHeader}
              ListFooterComponent={() => <View style={{height: 80}} />}
            />
          )}
        />
      </View>
    );
  },
);

const ItemSeparator = ({highlighted}) => (
  <RNTesterThemeContext.Consumer>
    {theme => {
      return (
        <View
          style={
            highlighted
              ? [
                  styles.separatorHighlighted,
                  {backgroundColor: theme.OpaqueSeparatorColor},
                ]
              : [styles.separator, {backgroundColor: theme.SeparatorColor}]
          }
        />
      );
    }}
  </RNTesterThemeContext.Consumer>
);

const styles = StyleSheet.create({
  listContainer: {
    flex: 1,
  },
  listItem: {
    backgroundColor: Platform.select({ios: '#FFFFFF', android: '#F3F8FF'}),
  },
  sectionHeader: {
    padding: 5,
    fontWeight: '500',
    fontSize: 11,
  },
  row: {
    justifyContent: 'center',
    paddingHorizontal: 15,
    paddingVertical: 12,
    marginVertical: Platform.select({ios: 4, android: 8}),
    marginHorizontal: 15,
    overflow: 'hidden',
    elevation: 5,
  },
  separator: {
    height: Platform.select({ios: StyleSheet.hairlineWidth, android: 0}),
    marginHorizontal: Platform.select({ios: 15, android: 0}),
  },
  separatorHighlighted: {
    height: StyleSheet.hairlineWidth,
  },
  topRowStyle: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    flex: 1,
  },
  bottomRowStyle: {
    flexDirection: 'row',
    justifyContent: 'space-between',
  },
  rowDetailText: {
    fontSize: 12,
    lineHeight: 20,
  },
  imageViewStyle: {
    height: 30,
    width: 30,
    borderRadius: 15,
    justifyContent: 'center',
    alignItems: 'center',
    position: 'relative',
    bottom: 5,
  },
  imageStyle: {
    height: 25,
    width: 25,
  },
  platformLabelStyle: {
    flexDirection: 'row',
    width: 100,
    justifyContent: 'space-between',
  },
});

module.exports = RNTesterExampleList;
<|endoftext|>


<commit_before><commit_msg>remove content<commit_after><|endoftext|>


<commit_before>/* jshint node: true */

'use strict';

/**
* Copyright 2015, Yahoo! Inc.
* Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/

let CachingWriter = require('broccoli-caching-writer');
let stringify = require('json-stable-stringify');
let mkdirp = require('mkdirp');
let extend = require('extend');
let yaml = require('js-yaml');
let glob = require('glob');
let path = require('path');
let fs = require('fs');

/**
* Turns an object into a single dimensional array of strings
*
* propKeys({ a: true, b: { c: true }}) => ["a", "b.c"]
*
* NOTE" Period within a key are escaped.
* ie, `propKeys({ 'a.a': true, b: { c: true }})` => `["a\.a", "b.c"]`
*
* @method propKeys
* @param {Object} object
* @return {Array} Returns array of strings
* @private
*/
function propKeys(object) {
  let result = [];
  let escaped;

  for (let key in object) {
    escaped = key.replace(/\./g, '\\.');

    if (object.hasOwnProperty(key)) {
      if (typeof object[key] === 'object') {
        result = result.concat(propKeys(object[key]).map(function(_key) {
          return escaped + '.' + _key;
        }));
      } else {
        result.push(escaped);
      }
    }
  }

  return result;
}

function readAsObject(filepath) {
  let data = fs.readFileSync(filepath);
  let ext = path.extname(filepath);

  switch (ext) {
    case '.json':
      return JSON.parse(data);
    case '.yaml':
    case '.yml':
      return yaml.load(data, 'utf8');
  }
}

class TranslationReducer extends CachingWriter {
  constructor(inputNode, options) {
    if (!Array.isArray(inputNode)) {
      inputNode = [inputNode];
    }

    super(inputNode, {
      annotation: 'Translation Reducer'
    });

    this.options = Object.assign({
      log() {}
    }, options);
  }

  normalizeLocale(locale) {
    if (typeof locale === 'string') {
      return locale.toLowerCase();
    }

    return locale;
  }

  _log(msg) {
    if (this.options.log) {
      return this.options.log.apply(undefined, arguments);
    }
  }

  findMissingKeys(target, defaultTranslationKeys, locale) {
    let targetProps = propKeys(target);

    defaultTranslationKeys.forEach(function(property) {
      if (targetProps.indexOf(property) === -1) {
        this._log(property + ' missing from ' + locale);
      }
    }, this);
  }

  readDirectory(inputPath, listFiles) {
    let plugin = this;

    // sorted so that any translation path starts with `__addon__`
    // move to the head of the array.  this ensures the application's translations
    // take presidence over addon translations.
    let sortedPaths = listFiles.sort(function(a, b) {
      if (a.indexOf('__addon__') === 0) {
        return -1;
      }

      return 1;
    });

    return sortedPaths.reduce(function(translations, translationPath) {
      if (fs.statSync(translationPath).isDirectory()) {
        return translations;
      }

      let translation = readAsObject(translationPath);

      if (!translation) {
        plugin._log('cannot read path "' + translationPath + '"');
        return translations;
      }

      let basename = path.basename(translationPath).split('.')[0];
      let keyedTranslation = {};
      keyedTranslation[plugin.normalizeLocale(basename)] = translation;

      return extend(true, translations, keyedTranslation);
    }, {});
  }

  filename(key) {
    if (typeof this.options.filename === 'function') {
      return this.options.filename(key);
    }

    return key + '.json';
  }

  wrapEntry(obj) {
    if (typeof this.options.wrapEntry === 'function') {
      return this.options.wrapEntry(obj);
    }

    return stringify(obj);
  }

  build() {
    let plugin = this;
    let inputPath = this.inputPaths[0];
    let outputPath = this.outputPath + '/' + this.options.outputPath;
    let baseLocale = this.options.baseLocale;
    let translations = this.readDirectory(inputPath, this.listFiles());
    let defaultTranslationKeys, defaultTranslation, translation;

    mkdirp.sync(outputPath);

    if (baseLocale) {
      let defaultTranslationPath = glob.sync(inputPath + '/' + baseLocale + '\.@(json|yaml|yml)', {
        nosort: true,
        silent: true
      })[0];

      if (!defaultTranslationPath) {
        plugin._log(baseLocale + ' default locale missing `translations` folder');
        return;
      }

      defaultTranslation = translations[this.normalizeLocale(baseLocale)];

      if (this.options.verbose) {
        defaultTranslationKeys = propKeys(defaultTranslation);
      }
    }

    for (let key in translations) {
      if (translations.hasOwnProperty(key)) {
        translation = translations[key];

        if (this.options.verbose && baseLocale && defaultTranslationKeys) {
          this.findMissingKeys(translation, defaultTranslationKeys, key);
        }

        translation = extend(true, {}, defaultTranslation, translation);

        fs.writeFileSync(outputPath + '/' + this.filename(key), this.wrapEntry(translation), { encoding: 'utf8' });
      }
    }
  }
}

module.exports = TranslationReducer;
<commit_msg>Correcting previous commit to be relative to the inputPath<commit_after>/* jshint node: true */

'use strict';

/**
* Copyright 2015, Yahoo! Inc.
* Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
*/

let CachingWriter = require('broccoli-caching-writer');
let stringify = require('json-stable-stringify');
let mkdirp = require('mkdirp');
let extend = require('extend');
let yaml = require('js-yaml');
let glob = require('glob');
let path = require('path');
let fs = require('fs');

/**
* Turns an object into a single dimensional array of strings
*
* propKeys({ a: true, b: { c: true }}) => ["a", "b.c"]
*
* NOTE" Period within a key are escaped.
* ie, `propKeys({ 'a.a': true, b: { c: true }})` => `["a\.a", "b.c"]`
*
* @method propKeys
* @param {Object} object
* @return {Array} Returns array of strings
* @private
*/
function propKeys(object) {
  let result = [];
  let escaped;

  for (let key in object) {
    escaped = key.replace(/\./g, '\\.');

    if (object.hasOwnProperty(key)) {
      if (typeof object[key] === 'object') {
        result = result.concat(propKeys(object[key]).map(function(_key) {
          return escaped + '.' + _key;
        }));
      } else {
        result.push(escaped);
      }
    }
  }

  return result;
}

function readAsObject(filepath) {
  let data = fs.readFileSync(filepath);
  let ext = path.extname(filepath);

  switch (ext) {
    case '.json':
      return JSON.parse(data);
    case '.yaml':
    case '.yml':
      return yaml.load(data, 'utf8');
  }
}

class TranslationReducer extends CachingWriter {
  constructor(inputNode, options) {
    if (!Array.isArray(inputNode)) {
      inputNode = [inputNode];
    }

    super(inputNode, {
      annotation: 'Translation Reducer'
    });

    this.options = Object.assign({
      log() {}
    }, options);
  }

  normalizeLocale(locale) {
    if (typeof locale === 'string') {
      return locale.toLowerCase();
    }

    return locale;
  }

  _log(msg) {
    if (this.options.log) {
      return this.options.log.apply(undefined, arguments);
    }
  }

  findMissingKeys(target, defaultTranslationKeys, locale) {
    let targetProps = propKeys(target);

    defaultTranslationKeys.forEach(function(property) {
      if (targetProps.indexOf(property) === -1) {
        this._log(property + ' missing from ' + locale);
      }
    }, this);
  }

  readDirectory(inputPath, listFiles) {
    let plugin = this;

    // sorted so that any translation path starts with `__addon__`
    // move to the head of the array.  this ensures the application's translations
    // take presidence over addon translations.
    let sortedPaths = listFiles.sort(function(a, b) {
      if (path.relative(inputPath, a).indexOf('__addon__') === 0) {
        return -1;
      }

      return 1;
    });

    return sortedPaths.reduce(function(translations, translationPath) {
      if (fs.statSync(translationPath).isDirectory()) {
        return translations;
      }

      let translation = readAsObject(translationPath);

      if (!translation) {
        plugin._log('cannot read path "' + translationPath + '"');
        return translations;
      }

      let basename = path.basename(translationPath).split('.')[0];
      let keyedTranslation = {};
      keyedTranslation[plugin.normalizeLocale(basename)] = translation;

      return extend(true, translations, keyedTranslation);
    }, {});
  }

  filename(key) {
    if (typeof this.options.filename === 'function') {
      return this.options.filename(key);
    }

    return key + '.json';
  }

  wrapEntry(obj) {
    if (typeof this.options.wrapEntry === 'function') {
      return this.options.wrapEntry(obj);
    }

    return stringify(obj);
  }

  build() {
    let plugin = this;
    let inputPath = this.inputPaths[0];
    let outputPath = this.outputPath + '/' + this.options.outputPath;
    let baseLocale = this.options.baseLocale;
    let translations = this.readDirectory(inputPath, this.listFiles());
    let defaultTranslationKeys, defaultTranslation, translation;

    mkdirp.sync(outputPath);

    if (baseLocale) {
      let defaultTranslationPath = glob.sync(inputPath + '/' + baseLocale + '\.@(json|yaml|yml)', {
        nosort: true,
        silent: true
      })[0];

      if (!defaultTranslationPath) {
        plugin._log(baseLocale + ' default locale missing `translations` folder');
        return;
      }

      defaultTranslation = translations[this.normalizeLocale(baseLocale)];

      if (this.options.verbose) {
        defaultTranslationKeys = propKeys(defaultTranslation);
      }
    }

    for (let key in translations) {
      if (translations.hasOwnProperty(key)) {
        translation = translations[key];

        if (this.options.verbose && baseLocale && defaultTranslationKeys) {
          this.findMissingKeys(translation, defaultTranslationKeys, key);
        }

        translation = extend(true, {}, defaultTranslation, translation);

        fs.writeFileSync(outputPath + '/' + this.filename(key), this.wrapEntry(translation), { encoding: 'utf8' });
      }
    }
  }
}

module.exports = TranslationReducer;
<|endoftext|>


<commit_before>import { mount } from '@vue/test-utils'
import Button from '@/components/Button'

describe('components/Button', () => {
  it('should create a button component with `primary` ui', () => {
    const wrapper = mount(Button, {
      propsData: {
        ui: 'primary'
      }
    })

    expect(wrapper.attributes('ui')).to.equal('primary')
  })

  it('should be disabled when the button is loading', () => {
    const SpinIcon = {
      template: 'Loading'
    }
    const wrapper = mount(Button, {
      propsData: {
        loading: true
      },
      slots: {
        loading: SpinIcon
      }
    })

    expect(wrapper.contains(SpinIcon)).to.equal(true)
    expect(wrapper.classes()).to.include('veui-button-loading')
    expect(wrapper.attributes('disabled')).to.equal('disabled')
  })

  it('should support disabled state', () => {
    const wrapper = mount(Button, {
      propsData: {
        disabled: true
      }
    })

    expect(wrapper.classes()).to.include('veui-disabled')
    expect(wrapper.attributes('disabled')).to.equal('disabled')
  })

  it('should support submit type', () => {
    const wrapper = mount(Button, {
      propsData: {
        type: 'submit'
      }
    })

    expect(wrapper.attributes('type')).to.include('submit')
  })

  it('should support set default slot', () => {
    const VItalic = {
      name: 'v-italic',
      template: 'Veui Button'
    }
    const wrapper = mount(Button, {
      slots: {
        default: VItalic
      }
    })

    expect(wrapper.contains(VItalic)).to.equal(true)
  })

  it('should handle click', async () => {
    const ButtonClick = {
      components: {
        'veui-button': Button
      },
      methods: {
        handleClick () {
          this.hasClick = true
        }
      },
      data () {
        return {
          hasClick: false
        }
      },
      template: '<veui-button @click="handleClick" ui="primary" />'
    }
    const wrapper = mount(ButtonClick)
    wrapper.find('.veui-button').trigger('click')
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.hasClick).to.equal(true)
  })
})
<commit_msg>test: make ut happy<commit_after>import { mount } from '@vue/test-utils'
import Button from '@/components/Button'

describe('components/Button', () => {
  it('should create a button component with `primary` ui', () => {
    const wrapper = mount(Button, {
      propsData: {
        ui: 'primary'
      }
    })

    expect(wrapper.attributes('ui').split(/\s+/)).to.include('primary')
  })

  it('should be disabled when the button is loading', () => {
    const SpinIcon = {
      template: 'Loading'
    }
    const wrapper = mount(Button, {
      propsData: {
        loading: true
      },
      slots: {
        loading: SpinIcon
      }
    })

    expect(wrapper.contains(SpinIcon)).to.equal(true)
    expect(wrapper.classes()).to.include('veui-button-loading')
    expect(wrapper.attributes('disabled')).to.equal('disabled')
  })

  it('should support disabled state', () => {
    const wrapper = mount(Button, {
      propsData: {
        disabled: true
      }
    })

    expect(wrapper.classes()).to.include('veui-disabled')
    expect(wrapper.attributes('disabled')).to.equal('disabled')
  })

  it('should support submit type', () => {
    const wrapper = mount(Button, {
      propsData: {
        type: 'submit'
      }
    })

    expect(wrapper.attributes('type')).to.include('submit')
  })

  it('should support set default slot', () => {
    const VItalic = {
      name: 'v-italic',
      template: 'Veui Button'
    }
    const wrapper = mount(Button, {
      slots: {
        default: VItalic
      }
    })

    expect(wrapper.contains(VItalic)).to.equal(true)
  })

  it('should handle click', async () => {
    const ButtonClick = {
      components: {
        'veui-button': Button
      },
      methods: {
        handleClick () {
          this.hasClick = true
        }
      },
      data () {
        return {
          hasClick: false
        }
      },
      template: '<veui-button @click="handleClick" ui="primary" />'
    }
    const wrapper = mount(ButtonClick)
    wrapper.find('.veui-button').trigger('click')
    await wrapper.vm.$nextTick()
    expect(wrapper.vm.hasClick).to.equal(true)
  })
})
<|endoftext|>