<commit_before>'use strict';

var I = require('immutable');

var generators  = require('../common/generators');
var seq         = require('../common/lazyseq');
var cosets      = require('../fpgroups/cosets');
var fundamental = require('./fundamental');
var derived     = require('./derived');
var covers      = require('./covers');

var _coreType = {
   1: 'z1',
   2: 'z2',
   3: 'z3',
   6: 's3',
   8: 'd4',
  12: 'a4',
  24: 's4'
};

var _fullyInvolutive = function _fullyInvolutive(ct) {
  return ct.every(function(row) {
    return row.keySeq().every(function(g) {
      return row.get(g) == row.get(-g);
    });
  });
};

var _traceWord = function _traceWord(ct, k, word) {
  return word.reduce(function(k, g) { return ct.getIn([k, g]); }, k);
};

var _degree = function _degree(ct, word) {
  var k = 0;
  for (var i = 1; ; ++i) {
    k = _traceWord(ct, k, word);
    if (k == 0)
      return i;
  }
};

var _flattensAll = function _flattensAll(ct, cones) {
  return cones.every(function(cone) {
    return _degree(ct, cone[0]) == cone[1];
  });
};

var _invariants = function _invariants(ds) {
  var fg = fundamental.fundamentalGroup(ds);
  return cosets.abelianInvariants(fg.nrGenerators, fg.relators);
};

var pseudoToroidalCover = function pseudoToroidalCover(ds) {
  ds = derived.orientedCover(ds);

  var fg = fundamental.fundamentalGroup(ds);
  var cones = fg.cones;

  if (cones.some(function(c) { return c[1] == 5 || c[1] > 6; }))
    throw new Error('violates the crystallographic restriction');

  var cones2 = cones.filter(function(c) { return c[1] == 2; });
  var cones3 = cones.filter(function(c) { return c[1] == 3; });

  var sub4 = generators.results(cosets.tables(fg.nrGenerators, fg.relators, 4));
  var base = I.List(seq.asArray(sub4)).map(cosets.coreTable);

  var cores = base.filter(function(ct) { return _flattensAll(ct, cones); })
    .map(function(ct) {
      if (ct.size == 4)
        return _fullyInvolutive(ct) ? ['v4', ct] : ['z4', ct];
      else
        return [_coreType[ct.size], ct];
    });

  var z2a = base.filter(function(ct) {
    return ct.size == 2 && _flattensAll(ct, cones2);
  });

  var z2b = base.filter(function(ct) {
    return ct.size == 2 && !_flattensAll(ct, cones2);
  });

  var z3a = base.filter(function(ct) {
    return ct.size == 3 && _flattensAll(ct, cones3);
  });

  var s3a = base.filter(function(ct) {
    return ct.size == 6 && _flattensAll(ct, cones3);
  });

  var z6 = z3a.flatMap(function(a) {
    return z2a
      .map(function(b) { return cosets.intersectionTable(a, b); })
      .filter(function(ct) {
        return ct.size == 6 && _flattensAll(ct, cones);
      })
      .map(function(ct) { return ['z6', ct]; });
  });

  var d6 = s3a.flatMap(function(a) {
    return z2b
      .map(function(b) { return cosets.intersectionTable(a, b); })
      .filter(function(ct) {
        return ct.size == 12 && _flattensAll(ct, cones);
      })
      .map(function(ct) { return ['d6', ct]; });
  });

  var categorized = I.List().concat(cores, z6, d6).groupBy(function(a) {
    return a[0];
  });
  var candidates = I.List('z1 z2 z3 z4 v4 s3 z6 d4 d6 a4 s4'.split(' '))
    .flatMap(function(type) {
      return (categorized.get(type) || I.List()).map(function(entry) {
        return covers.coverForTable(ds, entry[1], fg.edge2word);
      });
    });

  return candidates.filter(function(cov) {
    return _invariants(cov).equals(I.List([0,0,0]));
  }).first();
};

if (require.main == module) {
  var delaney = require('./delaney');

  var test = function test(ds) {
    console.log('ds = '+ds);
    var cov = pseudoToroidalCover(ds);
    console.log('cov = '+cov);
    console.log();
  }

  test(delaney.parse('<1.1:1 3:1,1,1,1:4,3,4>'));
}
<commit_msg>implement abelian invariants<commit_after>'use strict';

var I = require('immutable');
var Q = require('../arithmetic/number');
var M = require('../arithmetic/matrix')(Q, 0, 1);

var generators  = require('../common/generators');
var seq         = require('../common/lazyseq');
var cosets      = require('../fpgroups/cosets');
var fundamental = require('./fundamental');
var derived     = require('./derived');
var covers      = require('./covers');

var _coreType = {
   1: 'z1',
   2: 'z2',
   3: 'z3',
   6: 's3',
   8: 'd4',
  12: 'a4',
  24: 's4'
};

var _fullyInvolutive = function _fullyInvolutive(ct) {
  return ct.every(function(row) {
    return row.keySeq().every(function(g) {
      return row.get(g) == row.get(-g);
    });
  });
};

var _traceWord = function _traceWord(ct, k, word) {
  return word.reduce(function(k, g) { return ct.getIn([k, g]); }, k);
};

var _degree = function _degree(ct, word) {
  var k = 0;
  for (var i = 1; ; ++i) {
    k = _traceWord(ct, k, word);
    if (k == 0)
      return i;
  }
};

var _flattensAll = function _flattensAll(ct, cones) {
  return cones.every(function(cone) {
    return _degree(ct, cone[0]) == cone[1];
  });
};

var _isDiagonal = function _isDiagonal(mat) {
  return I.Range(0, mat.nrows).every(function(i) {
    return I.Range(0, mat.ncols).every(function(j) {
      return i == j || Q.sgn(M.get(mat, i, j)) == 0;
    });
  });
};

var _gcd = function _gcd(a, b) {
  a = Q.abs(a);
  b = Q.abs(b);

  while (Q.sgn(b) > 0) {
    var t = b;
    b = Q.mod(a, b);
    a = t;
  }

  return a;
};

var _factors = function _factors(xs) {
  return I.List(xs).withMutations(function(xs) {
    I.Range(0, xs.size).forEach(function(i) {
      var a = xs.get(i);
      I.Range(i+1, xs.size).forEach(function(j) {
        var b = xs.get(j);
        var g = _gcd(a, b);
        xs.set(j, Q.sqn(g) == 0 ? 0 : Q.times(Q.idiv(a, g), b));
        a = g;
      });
      xs.set(i, a);
    });
  });
};

var _invariants = function _invariants(ds) {
  var fg = fundamental.fundamentalGroup(ds);
  var mat = M.make(cosets.relatorMatrix(fg.nrGenerators, fg.relators));

  while (!_isDiagonal(mat)) {
    mat = M.transposed(M.triangulation(mat).R);
    mat = M.transposed(M.triangulation(mat).R);
  }

  var d = Math.min(mat.nrows, mat.ncols);
  var diag = I.Range(0, d).map(function(i) { return M.get(mat, i, i); });
  var factors = _factors(diag)
    .filter(function(x) { return Q.cmp(x, 1) != 0; })
    .sort();

  return I.Repeat(0, fg.nrGenerators - d).concat(factors);
};

var pseudoToroidalCover = function pseudoToroidalCover(ds) {
  ds = derived.orientedCover(ds);

  var fg = fundamental.fundamentalGroup(ds);
  var cones = fg.cones;

  if (cones.some(function(c) { return c[1] == 5 || c[1] > 6; }))
    throw new Error('violates the crystallographic restriction');

  var cones2 = cones.filter(function(c) { return c[1] == 2; });
  var cones3 = cones.filter(function(c) { return c[1] == 3; });

  var sub4 = generators.results(cosets.tables(fg.nrGenerators, fg.relators, 4));
  var base = I.List(seq.asArray(sub4)).map(cosets.coreTable);

  var cores = base.filter(function(ct) { return _flattensAll(ct, cones); })
    .map(function(ct) {
      if (ct.size == 4)
        return _fullyInvolutive(ct) ? ['v4', ct] : ['z4', ct];
      else
        return [_coreType[ct.size], ct];
    });

  var z2a = base.filter(function(ct) {
    return ct.size == 2 && _flattensAll(ct, cones2);
  });

  var z2b = base.filter(function(ct) {
    return ct.size == 2 && !_flattensAll(ct, cones2);
  });

  var z3a = base.filter(function(ct) {
    return ct.size == 3 && _flattensAll(ct, cones3);
  });

  var s3a = base.filter(function(ct) {
    return ct.size == 6 && _flattensAll(ct, cones3);
  });

  var z6 = z3a.flatMap(function(a) {
    return z2a
      .map(function(b) { return cosets.intersectionTable(a, b); })
      .filter(function(ct) {
        return ct.size == 6 && _flattensAll(ct, cones);
      })
      .map(function(ct) { return ['z6', ct]; });
  });

  var d6 = s3a.flatMap(function(a) {
    return z2b
      .map(function(b) { return cosets.intersectionTable(a, b); })
      .filter(function(ct) {
        return ct.size == 12 && _flattensAll(ct, cones);
      })
      .map(function(ct) { return ['d6', ct]; });
  });

  var categorized = I.List().concat(cores, z6, d6).groupBy(function(a) {
    return a[0];
  });
  var candidates = I.List('z1 z2 z3 z4 v4 s3 z6 d4 d6 a4 s4'.split(' '))
    .flatMap(function(type) {
      return (categorized.get(type) || I.List()).map(function(entry) {
        return covers.coverForTable(ds, entry[1], fg.edge2word);
      });
    });

  return candidates.filter(function(cov) {
    return _invariants(cov).equals(I.List([0,0,0]));
  }).first();
};

if (require.main == module) {
  var delaney = require('./delaney');

  var test = function test(ds) {
    console.log('ds = '+ds);
    var cov = pseudoToroidalCover(ds);
    console.log('cov = '+cov);
    console.log();
  }

  test(delaney.parse('<1.1:1 3:1,1,1,1:4,3,4>'));
}
<|endoftext|>


<commit_before>import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {Provider, connect} from "react-redux";

import {fetchAllAttendingActivities} from '../actions/activitiesActions';
import ActivityCardHomePage from '../components/ActivityCardHomePage';
import configureStore from "../configureStore";
import { withoutTime } from "../DateFunctions";
import '../../styles/activityBox.css';

const store = configureStore();

import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';

class AttendingActivitiesContainer extends Component {

    createActivityCardComponent = () => {
        return this.props.attendingActivities.map(activity => {
            return (
                <ActivityCardHomePage
                    key={activity.id + activity.fields.activityName}
                    id={activity.pk}
                    activity={activity.fields}
                />
            )
        });
    };

    render() {
        const styles = {
            activitiesContainerStyle: {
                padding: "1em"
            },
            activitiesStyle: {
                display: "flex",
                flexWrap: "wrap",
                flexDirection: "row",
                justifyContent: "flex-start",
                padding: "0.5em"
            },
        };

        let attendingContainer =
Du er ikke påmeldt noen aktiviteter
;
        if (this.props.attendingActivities.length > 0) {
            attendingContainer =

                    {this.createActivityCardComponent()}

        }

        return (

                {attendingContainer}

        );
    }

}

const mapStateToProps = state => {
    return {
        attendingActivities: state.activity.attendingActivityList
            .sort((a, b) => withoutTime(new Date(a.fields.date)) > withoutTime(new Date(b.fields.date))) // Sort descending based on date
            .filter((a) => withoutTime(new Date(a.fields.date_end)) >= withoutTime(new Date()))
        // Only get five first
    };
};

const mapDispatchToProps = dispatch => {
    return {
        fetchAttendingActivities: () => dispatch(fetchAllAttendingActivities()),
    }
};

const muiTheme = getMuiTheme({
    palette: {
        primary1Color: '#3F51B5',
    },
});

// Fetch initial data for state
store.dispatch(fetchAllAttendingActivities());

AttendingActivitiesContainer = connect(mapStateToProps, mapDispatchToProps)(AttendingActivitiesContainer);

ReactDOM.render(
    <MuiThemeProvider muiTheme={muiTheme}>
        <Provider store={store}>
            <AttendingActivitiesContainer />
        </Provider>
    </MuiThemeProvider>, document.getElementById('attendingActivities')
);

<commit_msg>cleaned attendingactivitiescontainer<commit_after>import React, {Component} from 'react';
import ReactDOM from 'react-dom';
import {Provider, connect} from "react-redux";
import {fetchAllAttendingActivities} from '../actions/activitiesActions';
import ActivityCardHomePage from '../components/ActivityCardHomePage';
import configureStore from "../configureStore";
import {withoutTime} from "../DateFunctions";
import '../../styles/activityBox.css';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import getMuiTheme from 'material-ui/styles/getMuiTheme';

const store = configureStore();

class AttendingActivitiesContainer extends Component {

    createActivityCardComponent = () => {
        return this.props.attendingActivities.map(activity => {
            return (
                <ActivityCardHomePage
                    key={activity.id + activity.fields.activityName}
                    id={activity.pk}
                    activity={activity.fields}
                />
            )
        });
    };

    render() {
        const styles = {
            activitiesContainerStyle: {
                padding: "1em"
            },
            activitiesStyle: {
                display: "flex",
                flexWrap: "wrap",
                flexDirection: "row",
                justifyContent: "flex-start",
                padding: "0.5em"
            },
        };

        let attendingContainer =
Du er ikke påmeldt noen aktiviteter
;
        if (this.props.attendingActivities.length > 0) {
            attendingContainer =

                    {this.createActivityCardComponent()}

        }

        return (

                {attendingContainer}

        );
    }
}

const mapStateToProps = state => {
    return {
        attendingActivities: state.activity.attendingActivityList
            .sort((a, b) => withoutTime(new Date(a.fields.date)) > withoutTime(new Date(b.fields.date)))
            .filter((a) => withoutTime(new Date(a.fields.date_end)) >= withoutTime(new Date()))
    };
};

const mapDispatchToProps = dispatch => {
    return {
        fetchAttendingActivities: () => dispatch(fetchAllAttendingActivities()),
    }
};

const muiTheme = getMuiTheme({
    palette: {
        primary1Color: '#3F51B5',
    },
});

// Fetch initial data for state
store.dispatch(fetchAllAttendingActivities());

AttendingActivitiesContainer = connect(mapStateToProps, mapDispatchToProps)(AttendingActivitiesContainer);

ReactDOM.render(
    <MuiThemeProvider muiTheme={muiTheme}>
        <Provider store={store}>
            <AttendingActivitiesContainer />
        </Provider>
    </MuiThemeProvider>, document.getElementById('attendingActivities')
);

<|endoftext|>


<commit_before>import React, { useState, useRef, useEffect } from "react";
import { prepareTitle } from "../common.js";
import Layout from "../components/layout";
import SEO from "../components/seo";
import { graphql } from "gatsby";
import Img from "gatsby-image";
import { Link } from "gatsby";
import {
  Citation,
  CitationButton,
  CitationText,
  CitationCopyNotice,
  Columns,
  CurrentDiscourse,
  Discourse,
  DiscourseNav,
  DiscourseTitle,
  EvenPageNumber,
  FirstPage,
  Flex,
  NextDiscourse,
  OddPageNumber,
  Page,
  PageHead,
  PageHeader,
  PreviousDiscourse,
  Reporter,
  SpeakerImage,
  Subtitle,
  SubtleLink
} from "./discourse.styles.js";

export default ({
  data,
  pageContext: {
    volumeNumbers,
    volumeNumber,
    discourse,
    previousDiscourse,
    nextDiscourse
  }
}) => {
  const [showCitation, setShowCitation] = useState(false);
  const [copySuccess, setCopySuccess] = useState(false);
  const citationSpanRef = useRef(null);

  useEffect(() => {
    if (showCitation && window.getSelection && citationSpanRef.current) {
      window.getSelection().removeAllRanges();
      const range = document.createRange();
      range.selectNode(citationSpanRef.current);
      window.getSelection().addRange(range);
      document.execCommand("copy");
      setCopySuccess(true);
    }
  }, [showCitation, citationSpanRef.current]);

  const seoTitle = `${prepareTitle(discourse.page_header)}, by ${
    discourse.speaker
  } (Journal of Discourses ${discourse.volume}:${discourse.start_page}-${
    discourse.end_page
  })`;

  const event = new Date();
  if (discourse.date) {
    event.setFullYear(parseInt(discourse.date.substr(0, 4), 10));
    event.setMonth(parseInt(discourse.date.substr(5, 7), 10) - 1);
    event.setDate(parseInt(discourse.date.substr(8, 10), 10));
    event.setUTCHours(0);
  }
  const dateOptions = {
    year: "numeric",
    month: "long",
    day: "numeric"
  };
  const formattedDate =
    discourse.date && event.toLocaleDateString("default", dateOptions);

  return (
    <Layout volumeNumbers={volumeNumbers} volumeNumber={volumeNumber}>
      <SEO title={seoTitle} description={prepareTitle(discourse.subtitle)} />
      <Discourse>
        <DiscourseNav>
          <PreviousDiscourse>
            {previousDiscourse && (
              <>
                ←
                <Link
                  to={`/${previousDiscourse.volume}/${previousDiscourse.start_page}`}
                  title={`${previousDiscourse.page_header}, by ${previousDiscourse.speaker}`}
                >
                  {previousDiscourse.volume !== discourse.volume && (
                    <>vol. {previousDiscourse.volume}, </>
                  )}
                  pp. {previousDiscourse.start_page}-
                  {previousDiscourse.end_page}
                </Link>
              </>
            )}
          </PreviousDiscourse>
          <CurrentDiscourse>
            vol. {volumeNumber}, pp. {discourse.start_page}-{discourse.end_page}
            <CitationButton onClick={() => setShowCitation(!showCitation)}>

            </CitationButton>
          </CurrentDiscourse>
          <NextDiscourse>
            {nextDiscourse && (
              <>
                <Link
                  to={`/${volumeNumber}/${nextDiscourse.start_page}`}
                  title={`${nextDiscourse.page_header}, by ${nextDiscourse.speaker}`}
                >
                  {nextDiscourse.volume !== discourse.volume && (
                    <>vol. {nextDiscourse.volume}, </>
                  )}
                  pp. {nextDiscourse.start_page}-{nextDiscourse.end_page}
                </Link>
                →
              </>
            )}
          </NextDiscourse>
        </DiscourseNav>
        {showCitation && (
          <Citation>
            <CitationText ref={citationSpanRef}>
              {discourse.speaker}, "{discourse.page_header}",{" "}
              Journal of Discourses, vol. {discourse.volume}, pp.{" "}
              {discourse.start_page}-{discourse.end_page}, {formattedDate}.
            </CitationText>
            {copySuccess && <CitationCopyNotice>(copied)</CitationCopyNotice>}
          </Citation>
        )}
        <FirstPage>
          <Flex>
            <DiscourseTitle
              dangerouslySetInnerHTML={{
                __html: prepareTitle(discourse.title)
              }}
            />
            <Subtitle>{prepareTitle(discourse.subtitle)}</Subtitle>
            <Reporter>
              Reported by {prepareTitle(discourse.reported_by)}.
            </Reporter>
            <SpeakerImage>
              {data.mug && (

              )}
            </SpeakerImage>
          </Flex>
        </FirstPage>
        {discourse.content.map((page, index) => {
          const pageNumber = discourse.start_page + index;
          return (
            <Page key={index} id={pageNumber}>
              <PageHead>
                <EvenPageNumber>
                  {pageNumber % 2 === 0 && (
                    <SubtleLink
                      to={`/${discourse.volume}/${discourse.start_page}#${pageNumber}`}
                    >
                      {pageNumber}
                    </SubtleLink>
                  )}
                </EvenPageNumber>
                <PageHeader
                  dangerouslySetInnerHTML={{
                    __html:
                      pageNumber % 2 === 1
                        ? prepareTitle(discourse.page_header)
                        : "Journal of Discourses"
                  }}
                />
                <OddPageNumber>
                  {pageNumber % 2 === 1 && (
                    <SubtleLink
                      to={`/${discourse.volume}/${discourse.start_page}#${pageNumber}`}
                    >
                      {pageNumber}
                    </SubtleLink>
                  )}
                </OddPageNumber>
              </PageHead>
              <Columns
                dangerouslySetInnerHTML={{
                  __html: page
                }}
              />
            </Page>
          );
        })}
      </Discourse>
    </Layout>
  );
};

export const query = graphql`
  query($mug: String!) {
    mug: file(relativePath: { eq: $mug }) {
      childImageSharp {
        fixed(width: 160, height: 200) {
          ...GatsbyImageSharpFixed_withWebp_noBase64
        }
      }
    }
    citationImage: file(relativePath: { eq: "cite.png" }) {
      childImageSharp {
        # Specify the image processing specifications right in the query.
        # Makes it trivial to update as your page's design changes.
        fixed(width: 18, height: 18) {
          ...GatsbyImageSharpFixed_tracedSVG
        }
      }
    }
  }
`;
<commit_msg>NextDiscourse link fix<commit_after>import React, { useState, useRef, useEffect } from "react";
import { prepareTitle } from "../common.js";
import Layout from "../components/layout";
import SEO from "../components/seo";
import { graphql } from "gatsby";
import Img from "gatsby-image";
import { Link } from "gatsby";
import {
  Citation,
  CitationButton,
  CitationText,
  CitationCopyNotice,
  Columns,
  CurrentDiscourse,
  Discourse,
  DiscourseNav,
  DiscourseTitle,
  EvenPageNumber,
  FirstPage,
  Flex,
  NextDiscourse,
  OddPageNumber,
  Page,
  PageHead,
  PageHeader,
  PreviousDiscourse,
  Reporter,
  SpeakerImage,
  Subtitle,
  SubtleLink
} from "./discourse.styles.js";

export default ({
  data,
  pageContext: {
    volumeNumbers,
    volumeNumber,
    discourse,
    previousDiscourse,
    nextDiscourse
  }
}) => {
  const [showCitation, setShowCitation] = useState(false);
  const [copySuccess, setCopySuccess] = useState(false);
  const citationSpanRef = useRef(null);

  useEffect(() => {
    if (showCitation && window.getSelection && citationSpanRef.current) {
      window.getSelection().removeAllRanges();
      const range = document.createRange();
      range.selectNode(citationSpanRef.current);
      window.getSelection().addRange(range);
      document.execCommand("copy");
      setCopySuccess(true);
    }
  }, [showCitation, citationSpanRef.current]);

  const seoTitle = `${prepareTitle(discourse.page_header)}, by ${
    discourse.speaker
  } (Journal of Discourses ${discourse.volume}:${discourse.start_page}-${
    discourse.end_page
  })`;

  const event = new Date();
  if (discourse.date) {
    event.setFullYear(parseInt(discourse.date.substr(0, 4), 10));
    event.setMonth(parseInt(discourse.date.substr(5, 7), 10) - 1);
    event.setDate(parseInt(discourse.date.substr(8, 10), 10));
    event.setUTCHours(0);
  }
  const dateOptions = {
    year: "numeric",
    month: "long",
    day: "numeric"
  };
  const formattedDate =
    discourse.date && event.toLocaleDateString("default", dateOptions);

  return (
    <Layout volumeNumbers={volumeNumbers} volumeNumber={volumeNumber}>
      <SEO title={seoTitle} description={prepareTitle(discourse.subtitle)} />
      <Discourse>
        <DiscourseNav>
          <PreviousDiscourse>
            {previousDiscourse && (
              <>
                ←
                <Link
                  to={`/${previousDiscourse.volume}/${previousDiscourse.start_page}`}
                  title={`${previousDiscourse.page_header}, by ${previousDiscourse.speaker}`}
                >
                  {previousDiscourse.volume !== discourse.volume && (
                    <>vol. {previousDiscourse.volume}, </>
                  )}
                  pp. {previousDiscourse.start_page}-
                  {previousDiscourse.end_page}
                </Link>
              </>
            )}
          </PreviousDiscourse>
          <CurrentDiscourse>
            vol. {volumeNumber}, pp. {discourse.start_page}-{discourse.end_page}
            <CitationButton onClick={() => setShowCitation(!showCitation)}>

            </CitationButton>
          </CurrentDiscourse>
          <NextDiscourse>
            {nextDiscourse && (
              <>
                <Link
                  to={`/${nextDiscourse.volume}/${nextDiscourse.start_page}`}
                  title={`${nextDiscourse.page_header}, by ${nextDiscourse.speaker}`}
                >
                  {nextDiscourse.volume !== discourse.volume && (
                    <>vol. {nextDiscourse.volume}, </>
                  )}
                  pp. {nextDiscourse.start_page}-{nextDiscourse.end_page}
                </Link>
                →
              </>
            )}
          </NextDiscourse>
        </DiscourseNav>
        {showCitation && (
          <Citation>
            <CitationText ref={citationSpanRef}>
              {discourse.speaker}, "{discourse.page_header}",{" "}
              Journal of Discourses, vol. {discourse.volume}, pp.{" "}
              {discourse.start_page}-{discourse.end_page}, {formattedDate}.
            </CitationText>
            {copySuccess && <CitationCopyNotice>(copied)</CitationCopyNotice>}
          </Citation>
        )}
        <FirstPage>
          <Flex>
            <DiscourseTitle
              dangerouslySetInnerHTML={{
                __html: prepareTitle(discourse.title)
              }}
            />
            <Subtitle>{prepareTitle(discourse.subtitle)}</Subtitle>
            <Reporter>
              Reported by {prepareTitle(discourse.reported_by)}.
            </Reporter>
            <SpeakerImage>
              {data.mug && (

              )}
            </SpeakerImage>
          </Flex>
        </FirstPage>
        {discourse.content.map((page, index) => {
          const pageNumber = discourse.start_page + index;
          return (
            <Page key={index} id={pageNumber}>
              <PageHead>
                <EvenPageNumber>
                  {pageNumber % 2 === 0 && (
                    <SubtleLink
                      to={`/${discourse.volume}/${discourse.start_page}#${pageNumber}`}
                    >
                      {pageNumber}
                    </SubtleLink>
                  )}
                </EvenPageNumber>
                <PageHeader
                  dangerouslySetInnerHTML={{
                    __html:
                      pageNumber % 2 === 1
                        ? prepareTitle(discourse.page_header)
                        : "Journal of Discourses"
                  }}
                />
                <OddPageNumber>
                  {pageNumber % 2 === 1 && (
                    <SubtleLink
                      to={`/${discourse.volume}/${discourse.start_page}#${pageNumber}`}
                    >
                      {pageNumber}
                    </SubtleLink>
                  )}
                </OddPageNumber>
              </PageHead>
              <Columns
                dangerouslySetInnerHTML={{
                  __html: page
                }}
              />
            </Page>
          );
        })}
      </Discourse>
    </Layout>
  );
};

export const query = graphql`
  query($mug: String!) {
    mug: file(relativePath: { eq: $mug }) {
      childImageSharp {
        fixed(width: 160, height: 200) {
          ...GatsbyImageSharpFixed_withWebp_noBase64
        }
      }
    }
    citationImage: file(relativePath: { eq: "cite.png" }) {
      childImageSharp {
        # Specify the image processing specifications right in the query.
        # Makes it trivial to update as your page's design changes.
        fixed(width: 18, height: 18) {
          ...GatsbyImageSharpFixed_tracedSVG
        }
      }
    }
  }
`;
<|endoftext|>


<commit_before>const XML = require('pixl-xml')
const _ = require('lodash')

const parseBool = s => s === 'TRUE'
const getArrayNode = node => Array.from(Array.isArray(node) ? node : [node])

const getStyleType = type =>
  type === 'lager' || type === 'ale' ? 'beer' : type

const potential = y => (y * 0.01 * 46) / 1000 + 1

const mash_steps = r =>
  _.map(
    getArrayNode(_.get(r, ['MASH', 'MASH_STEPS', 'MASH_STEP'])),
    mash_step => ({
      name: mash_step['NAME'],
      type: _.lowerCase(mash_step['TYPE']),
      step_temperature: {
        unit: 'C',
        value: Number(mash_step['STEP_TEMP'])
      },
      step_time: {
        unit: 'min',
        value: Number(mash_step['STEP_TIME'])
      },
      ...(!_.isEmpty(mash_step['RAMP_TIME'])
        ? {
            ramp_time: {
              unit: 'min',
              value: Number(mash_step['RAMP_TIME'])
            }
          }
        : {}),
      ...(!_.isEmpty(mash_step['END_TEMP'])
        ? {
            end_temperature: {
              unit: 'C',
              value: Number(mash_step['END_TEMP'])
            }
          }
        : {}),
      ...(!_.isEmpty(mash_step['INFUSE_AMOUNT'])
        ? {
            amount: {
              unit: 'l',
              value: Number(mash_step['INFUSE_AMOUNT'])
            }
          }
        : {})
    })
  )

const mash = r => {
  return {
    name: _.get(r, ['MASH', 'NAME']),
    grain_temperature: {
      unit: 'C',
      value: Number(_.get(r, ['MASH', 'GRAIN_TEMP']))
    },
    notes: _.get(r, ['MASH', 'NOTES']),
    mash_steps: !_.isEmpty(_.get(r, ['MASH', 'SPARGE_TEMP']))
      ? [
          ...mash_steps(r),
          {
            name: 'Sparge',
            type: 'sparge',
            step_time: {
              unit: 'min',
              value: 15
            },
            step_temperature: {
              unit: 'C',
              value: Number(_.get(r, ['MASH', 'SPARGE_TEMP']))
            }
          }
        ]
      : mash_steps(r)
  }
}

const water_additions = r =>
  _.map(getArrayNode(_.get(r, ['WATERS', 'WATER'])), water => ({
    name: water['NAME'],
    calcium: Number(water['CALCIUM']),
    bicarbonate: Number(water['BICARBONATE']),
    sulfate: Number(water['SULFATE']),
    chloride: Number(water['CHLORIDE']),
    sodium: Number(water['SODIUM']),
    magnesium: Number(water['MAGNESIUM']),
    amount: {
      unit: 'l',
      value: Number(water['AMOUNT'])
    }
  }))

const culture_additions = r =>
  _.map(getArrayNode(_.get(r, ['YEASTS', 'YEAST'])), culture => ({
    name: culture['NAME'],
    ...(!_.isEmpty(culture['ATTENUATION'])
      ? {
          attenuation: {
            unit: '%',
            value: Number(culture['ATTENUATION'])
          }
        }
      : {}),
    type: _.lowerCase(culture['TYPE']),
    form: _.lowerCase(culture['FORM']),
    ...(!_.isEmpty(culture['AMOUNT_IS_WEIGHT']) &&
    parseBool(culture['AMOUNT_IS_WEIGHT'])
      ? {
          amount: {
            unit: 'kg',
            value: Number(culture['AMOUNT'])
          }
        }
      : {}),
    ...((!_.isEmpty(culture['AMOUNT_IS_WEIGHT']) &&
      !parseBool(culture['AMOUNT_IS_WEIGHT'])) ||
    _.isEmpty(culture['AMOUNT_IS_WEIGHT'])
      ? {
          amount: {
            unit: 'l',
            value: Number(culture['AMOUNT'])
          }
        }
      : {})
  }))

const miscellaneous_additions = r =>
  _.map(getArrayNode(_.get(r, ['MISCS', 'MISC'])), misc => ({
    name: misc['NAME'],
    type: _.lowerCase(misc['TYPE']),
    use: _.lowerCase(misc['USE']),
    ...(!_.isEmpty(misc['AMOUNT_IS_WEIGHT']) &&
    parseBool(misc['AMOUNT_IS_WEIGHT'])
      ? {
          amount: {
            unit: 'kg',
            value: Number(misc['AMOUNT'])
          }
        }
      : {}),
    ...((!_.isEmpty(misc['AMOUNT_IS_WEIGHT']) &&
      !parseBool(misc['AMOUNT_IS_WEIGHT'])) ||
    _.isEmpty(misc['AMOUNT_IS_WEIGHT'])
      ? {
          amount: {
            unit: 'l',
            value: Number(misc['AMOUNT'])
          }
        }
      : {})
  }))

const hop_bill = r =>
  _.map(getArrayNode(_.get(r, ['HOPS', 'HOP'])), hop => ({
    name: hop['NAME'],
    alpha_acid: {
      unit: '%',
      value: Number(hop['ALPHA'])
    },
    ...(!_.isEmpty(hop['ORIGIN']) ? { origin: hop['ORIGIN'] } : {}),
    ...(!_.isEmpty(hop['FORM']) ? { form: _.lowerCase(hop['FORM']) } : {}),
    ...(!_.isEmpty(hop['BETA'])
      ? {
          beta_acid: {
            unit: '%',
            value: Number(hop['BETA'])
          }
        }
      : {}),
    ...(!_.isEmpty(hop['USE']) ? { use: _.lowerCase(hop['USE']) } : {}),
    amount: {
      unit: 'kg',
      value: Number(hop['AMOUNT'])
    },
    timing: {
      time: {
        unit: 'min',
        value: Number(hop['TIME'])
      },
      use: 'add_to_boil'
    }
  }))

const fermentable_bill = r =>
  _.map(
    getArrayNode(_.get(r, ['FERMENTABLES', 'FERMENTABLE'])),
    fermentable => ({
      name: fermentable['NAME'],
      type: _.lowerCase(fermentable['TYPE']),
      color: {
        unit: 'Lovi',
        value: Number(fermentable['COLOR'])
      },
      amount: {
        unit: 'kg',
        value: Number(fermentable['AMOUNT'])
      },
      origin: fermentable['ORIGIN'],
      producer: fermentable['SUPPLIER'],
      group: 'base',
      yield: {
        potential: {
          unit: 'sg',
          value: potential(Number(fermentable['YIELD']))
        }
      },
      ...(!_.isEmpty(fermentable['ADD_AFTER_BOIL'])
        ? {
            add_after_boil: parseBool(fermentable['ADD_AFTER_BOIL'])
          }
        : {})
    })
  )

const importFromBeerXml = xml => {
  try {
    const r = XML.parse(xml).RECIPE
    const recipe = {
      beerjson: {
        version: 2.06,
        recipes: [
          {
            name: r['NAME'],
            type: _.lowerCase(r['TYPE']),
            author: r['BREWER'],
            ...(!_.isEmpty(r['ASST_BREWER'])
              ? { coauthor: r['ASST_BREWER'] }
              : {}),
            ...(!_.isEmpty(r['DATE'])
              ? { created: new Date(r['DATE']).toISOString() }
              : {}),
            batch_size: {
              unit: 'l',
              value: Number(r['BATCH_SIZE'])
            },
            boil: {
              pre_boil_size: {
                unit: 'l',
                value: Number(r['BOIL_SIZE'])
              },
              boil_time: {
                unit: 'min',
                value: Number(r['BOIL_TIME'])
              }
            },
            efficiency: {
              brewhouse: {
                unit: '%',
                value: Number(r['EFFICIENCY'])
              }
            },
            ...(!_.isEmpty(r['STYLE'])
              ? {
                  style: {
                    name: _.get(r, ['STYLE', 'NAME']),
                    category: _.get(r, ['STYLE', 'CATEGORY']),
                    style_guide: _.get(r, ['STYLE', 'STYLE_GUIDE']),
                    type: getStyleType(_.lowerCase(_.get(r, ['STYLE', 'TYPE'])))
                  }
                }
              : {}),
            ingredients: {
              fermentable_additions: fermentable_bill(r),
              hop_additions: hop_bill(r),
              ...(!_.isEmpty(r['MISCS'])
                ? {
                    miscellaneous_additions: miscellaneous_additions(r)
                  }
                : {}),
              culture_additions: culture_additions(r),
              ...(!_.isEmpty(r['WATERS'])
                ? {
                    water_additions: water_additions(r)
                  }
                : {})
            },
            mash: mash(r)
          }
        ]
      }
    }

    return JSON.stringify(recipe)
  } catch (err) {
    console.log('XML Parser Error: ' + err)
    throw err
  }
}

module.exports = importFromBeerXml
<commit_msg>Update xml to beerjson converter (#184)<commit_after>const XML = require('pixl-xml')
const {
  isNil,
  get,
  map,
  every,
  isEmpty,
  toLower,
  lowerCase
} = require('lodash')

const parseBool = s => s === 'TRUE'
const getArrayNode = node =>
  Array.from(isNil(node) ? [] : Array.isArray(node) ? node : [node])

const getStyleType = type =>
  type === 'lager' || type === 'ale' || type === '' ? 'beer' : type
const getFermentableType = type =>
  [
    'dry extract',
    'extract',
    'grain',
    'sugar',
    'fruit',
    'juice',
    'honey',
    'other'
  ].indexOf(type) == -1
    ? 'other'
    : type

const potential = y => (y * 0.46) / 1000 + 1
const mash_steps = r =>
  map(getArrayNode(get(r, ['MASH', 'MASH_STEPS', 'MASH_STEP'])), mash_step => ({
    name: mash_step['NAME'],
    type: lowerCase(mash_step['TYPE']),
    description: mash_step['DESCRIPTION'],
    step_temperature: {
      unit: 'C',
      value: Number(mash_step['STEP_TEMP'])
    },
    step_time: {
      unit: 'min',
      value: Number(mash_step['STEP_TIME'])
    },
    ...(!isEmpty(mash_step['RAMP_TIME'])
      ? {
          ramp_time: {
            unit: 'min',
            value: Number(mash_step['RAMP_TIME'])
          }
        }
      : {}),
    ...(!isEmpty(mash_step['END_TEMP'])
      ? {
          end_temperature: {
            unit: 'C',
            value: Number(mash_step['END_TEMP'])
          }
        }
      : {}),
    ...(!isEmpty(mash_step['INFUSE_AMOUNT'])
      ? {
          amount: {
            unit: 'l',
            value: Number(mash_step['INFUSE_AMOUNT'])
          }
        }
      : {})
  }))

const mash = r => {
  return {
    name: get(r, ['MASH', 'NAME']),
    grain_temperature: {
      unit: 'C',
      value: Number(get(r, ['MASH', 'GRAIN_TEMP']))
    },
    notes: get(r, ['MASH', 'NOTES']),
    mash_steps: mash_steps(r)
  }
}

const water_additions = r =>
  map(getArrayNode(get(r, ['WATERS', 'WATER'])), water => ({
    name: water['NAME'],
    calcium: {
      unit: 'mg/l',
      value: Number(water['CALCIUM'])
    },
    bicarbonate: {
      unit: 'mg/l',
      value: Number(water['BICARBONATE'])
    },
    sulfate: {
      unit: 'mg/l',
      value: Number(water['SULFATE'])
    },
    chloride: {
      unit: 'mg/l',
      value: Number(water['CHLORIDE'])
    },
    sodium: {
      unit: 'mg/l',
      value: Number(water['SODIUM'])
    },
    magnesium: {
      unit: 'mg/l',
      value: Number(water['MAGNESIUM'])
    },
    amount: {
      unit: 'l',
      value: Number(water['AMOUNT'])
    }
  }))

const miscellaneous_additions = r =>
  map(getArrayNode(get(r, ['MISCS', 'MISC'])), misc => ({
    name: misc['NAME'],
    type: lowerCase(misc['TYPE']),
    use: lowerCase(misc['USE']),
    ...(!isEmpty(misc['AMOUNT_IS_WEIGHT']) &&
    parseBool(misc['AMOUNT_IS_WEIGHT'])
      ? {
          amount: {
            unit: 'kg',
            value: Number(misc['AMOUNT'])
          }
        }
      : {}),
    ...((!isEmpty(misc['AMOUNT_IS_WEIGHT']) &&
      !parseBool(misc['AMOUNT_IS_WEIGHT'])) ||
    isEmpty(misc['AMOUNT_IS_WEIGHT'])
      ? {
          amount: {
            unit: 'l',
            value: Number(misc['AMOUNT'])
          }
        }
      : {}),
    timing: {
      time: {
        unit: 'min',
        value: Number(misc['TIME'])
      },
      use: 'add_to_boil'
    }
  }))

const hop_bill = r =>
  map(getArrayNode(get(r, ['HOPS', 'HOP'])), hop => ({
    name: hop['NAME'],
    alpha_acid: {
      unit: '%',
      value: Number(hop['ALPHA'])
    },
    ...(!isEmpty(hop['ORIGIN']) ? { origin: hop['ORIGIN'] } : {}),
    ...(!isEmpty(hop['FORM']) ? { form: lowerCase(hop['FORM']) } : {}),
    ...(!isEmpty(hop['BETA'])
      ? {
          beta_acid: {
            unit: '%',
            value: Number(hop['BETA'])
          }
        }
      : {}),
    ...(!isEmpty(hop['USE']) ? { use: lowerCase(hop['USE']) } : {}),
    amount: {
      unit: 'kg',
      value: Number(hop['AMOUNT'])
    },
    timing: {
      time: {
        unit: 'min',
        value: Number(hop['TIME'])
      },
      use: 'add_to_boil'
    }
  }))

const fermentable_bill = r =>
  map(getArrayNode(get(r, ['FERMENTABLES', 'FERMENTABLE'])), fermentable => ({
    name: fermentable['NAME'],
    type: getFermentableType(lowerCase(fermentable['TYPE'])),
    color: {
      unit: 'Lovi',
      value: Number(fermentable['COLOR'])
    },
    amount: {
      unit: 'kg',
      value: Number(fermentable['AMOUNT'])
    },
    origin: fermentable['ORIGIN'],
    supplier: fermentable['SUPPLIER'],
    group: 'base',
    yield: {
      potential: {
        unit: 'sg',
        value: potential(Number(fermentable['YIELD']))
      }
    },
    ...(!isEmpty(fermentable['ADD_AFTER_BOIL'])
      ? {
          add_after_boil: parseBool(fermentable['ADD_AFTER_BOIL'])
        }
      : {})
  }))

const DEFAULT_ATTENUATION = 75

const importCultureAdditions = recipe => {
  const cultures = get(recipe, 'YEASTS.YEAST')

  if (isEmpty(cultures)) {
    return []
  }

  return map(getArrayNode(cultures), culture => ({
    name: culture.NAME,
    type: toLower(culture.TYPE),
    form: toLower(culture.FORM),
    amount: {
      unit: parseBool(culture.AMOUNT_IS_WEIGHT) ? 'kg' : 'l',
      value: Number(culture.AMOUNT)
    },
    attenuation: {
      unit: '%',
      value: isEmpty(culture.ATTENUATION)
        ? DEFAULT_ATTENUATION
        : Number(culture.ATTENUATION)
    }
  }))
}

const checkRequiredFields = (obj, fieldList) =>
  every(fieldList, field => !isEmpty(obj[field]))

const importStyle = recipe => {
  const { STYLE } = recipe

  if (
    isEmpty(STYLE) ||
    !checkRequiredFields(STYLE, [
      'NAME',
      'CATEGORY',
      'STYLE_GUIDE',
      'TYPE',
      'OG_MIN',
      'OG_MAX',
      'FG_MIN',
      'FG_MAX',
      'IBU_MIN',
      'IBU_MAX',
      'COLOR_MIN',
      'COLOR_MAX'
    ])
  ) {
    return null
  }

  return {
    name: STYLE.NAME,
    category: STYLE.CATEGORY,
    style_guide: STYLE.STYLE_GUIDE,
    type: getStyleType(toLower(STYLE.TYPE)),
    original_gravity: {
      minimum: {
        unit: 'sg',
        value: Number(STYLE.OG_MIN)
      },
      maximum: {
        unit: 'sg',
        value: Number(STYLE.OG_MAX)
      }
    },
    final_gravity: {
      minimum: {
        unit: 'sg',
        value: Number(STYLE.FG_MIN)
      },
      maximum: {
        unit: 'sg',
        value: Number(STYLE.FG_MAX)
      }
    },
    international_bitterness_units: {
      minimum: {
        unit: 'IBUs',
        value: Number(STYLE.IBU_MIN)
      },
      maximum: {
        unit: 'IBUs',
        value: Number(STYLE.IBU_MAX)
      }
    },
    alcohol_by_volume: {
      minimum: {
        unit: '%',
        value: Number(STYLE.ABV_MIN)
      },
      maximum: {
        unit: '%',
        value: Number(STYLE.ABV_MAX)
      }
    },
    color: {
      minimum: {
        unit: 'srm',
        value: Number(STYLE.COLOR_MIN)
      },
      maximum: {
        unit: 'srm',
        value: Number(STYLE.COLOR_MAX)
      }
    }
  }
}

const importFromBeerXml = xml => {
  try {
    const r = XML.parse(xml).RECIPE
    if (Array.isArray(r)) {
      throw new Error(
        'Converter does not currently support importing several recipes from a single BeerXML file. Please create a separate file for each recipe.'
      )
    }
    const recipe = {
      name: r['NAME'],
      type: lowerCase(r['TYPE']),
      author: r['BREWER'],
      ...(!isEmpty(r['ASST_BREWER']) ? { coauthor: r['ASST_BREWER'] } : {}),
      ...(!isEmpty(r['DATE'])
        ? { created: new Date(r['DATE']).toISOString() }
        : {}),
      batch_size: {
        unit: 'l',
        value: Number(r['BATCH_SIZE'])
      },
      boil: {
        pre_boil_size: {
          unit: 'l',
          value: Number(r['BOIL_SIZE'])
        },
        boil_time: {
          unit: 'min',
          value: Number(r['BOIL_TIME'])
        }
      },
      efficiency: {
        brewhouse: {
          unit: '%',
          value: Number(r['EFFICIENCY'])
        }
      },
      ingredients: {
        fermentable_additions: fermentable_bill(r),
        hop_additions: hop_bill(r),
        ...(!isEmpty(r['MISCS'])
          ? {
              miscellaneous_additions: miscellaneous_additions(r)
            }
          : {}),
        culture_additions: importCultureAdditions(r),
        ...(!isEmpty(r['WATERS'])
          ? {
              water_additions: water_additions(r)
            }
          : {})
      },
      mash: mash(r)
    }

    const style = importStyle(r)
    if (style != null) {
      recipe.style = style
    }

    const beerJSON = {
      beerjson: {
        version: 2.06,
        recipes: [recipe]
      }
    }

    return JSON.stringify(beerJSON)
  } catch (err) {
    console.log('XML Parser Error: ' + err)
    throw err
  }
}

module.exports = importFromBeerXml
<|endoftext|>


<commit_before>var jaylist = new List();

module("jaylist Module");

test('Add items', function() {
    expect(4);
    equals(jaylist.add("that", 1), 1, 'Added ("that", 1).');
    equals(jaylist.add("this", 4), 4, 'Added ("this", 4).');
    equals(jaylist.add("this", 7), 7, 'Overwrite ("this", 4) with ("this", 7).');
    equals(jaylist.add("other", "things"), "things", 'Added ("other", "things").');
});

test('Contain functions', function() {
    expect(3);
    equals(jaylist.hasKey("that"), true, 'Has key "that".');
    equals(jaylist.hasKey("this"), true, 'Has key "this".');
    equals(jaylist.hasKey("Missing"), false, 'Does not have key "Missing".');
});

test('Get items', function() {
    expect(4);
    equals(jaylist.get("that"), 1, 'Retrieved the value of "that" (1).');
    equals(jaylist.get("this"), 7, 'Retrieved the value of "this" (7).');
    equals(jaylist.get("other"), "things", 'Retrieved the value of "other" ("things").');
    equals(jaylist.get("Missing"), undefined, 'Cannot retrieve a value not in the list (undefined).');
});

test('Remove items', function() {
    expect(4);
    jaylist.remove("that");
    equals(jaylist.get("that"), undefined, 'Removed "that".');
    jaylist.remove("this");
    equals(jaylist.get("this"), undefined, 'Removed "this".');
    jaylist.remove("other");
    equals(jaylist.get("other"), undefined, 'Removed "other".');
    equals(jaylist.remove("Missing"), false, 'Cannot remove value not in the list.');
});

test('Pop function', function () {
    expect(1);
    jaylist.add("popped", 4);
    jaylist.pop("popped");
    ok(!jaylist.hasKey("popped"), 'Popped key.');
});

test('Mass retrieval', function() {
    expect(2);
    jaylist.add("howdy", 9);
    jaylist.add("amazing", 9);
    equals(jaylist.keys().toString(), "howdy,amazing", 'Retrieved keys.');
    equals(jaylist.values().toString(), "9", 'Retrieved values.');
});

test('Length functions', function() {
    expect(2);
    equals(jaylist.len(), 2, 'Retrieved length.');
    var emptylist = new List();
    equals(emptylist.len(), 0, 'Retrieved empty list length.')
});

test('Clear functions', function() {
    expect(1);
    jaylist.clear();
    equals(jaylist.len(), 0, 'Cleared list.');
});
<commit_msg>added pop tests for non-existant keys<commit_after>var jaylist = new List();

module("jaylist Module");

test('Add items', function() {
    expect(4);
    equals(jaylist.add("that", 1), 1, 'Added ("that", 1).');
    equals(jaylist.add("this", 4), 4, 'Added ("this", 4).');
    equals(jaylist.add("this", 7), 7, 'Overwrite ("this", 4) with ("this", 7).');
    equals(jaylist.add("other", "things"), "things", 'Added ("other", "things").');
});

test('Contain functions', function() {
    expect(3);
    equals(jaylist.hasKey("that"), true, 'Has key "that".');
    equals(jaylist.hasKey("this"), true, 'Has key "this".');
    equals(jaylist.hasKey("Missing"), false, 'Does not have key "Missing".');
});

test('Get items', function() {
    expect(4);
    equals(jaylist.get("that"), 1, 'Retrieved the value of "that" (1).');
    equals(jaylist.get("this"), 7, 'Retrieved the value of "this" (7).');
    equals(jaylist.get("other"), "things", 'Retrieved the value of "other" ("things").');
    equals(jaylist.get("Missing"), undefined, 'Cannot retrieve a value not in the list (undefined).');
});

test('Remove items', function() {
    expect(4);
    jaylist.remove("that");
    equals(jaylist.get("that"), undefined, 'Removed "that".');
    jaylist.remove("this");
    equals(jaylist.get("this"), undefined, 'Removed "this".');
    jaylist.remove("other");
    equals(jaylist.get("other"), undefined, 'Removed "other".');
    equals(jaylist.remove("Missing"), false, 'Cannot remove value not in the list.');
});

test('Pop function', function () {
    expect(3);
    jaylist.add("popped", 4);
    jaylist.pop("popped");
    ok(!jaylist.hasKey("popped"), 'Popped key.');
    equals(jaylist.pop("Missing", 80), 80, 'Popping missing key returns default.');
    equals(jaylist.pop("Missing"), undefined, 'Popping missing key returns undefined.');
});

test('Mass retrieval', function() {
    expect(2);
    jaylist.add("howdy", 9);
    jaylist.add("amazing", 9);
    equals(jaylist.keys().toString(), "howdy,amazing", 'Retrieved keys.');
    equals(jaylist.values().toString(), "9", 'Retrieved values.');
});

test('Length functions', function() {
    expect(2);
    equals(jaylist.len(), 2, 'Retrieved length.');
    var emptylist = new List();
    equals(emptylist.len(), 0, 'Retrieved empty list length.')
});

test('Clear functions', function() {
    expect(1);
    jaylist.clear();
    equals(jaylist.len(), 0, 'Cleared list.');
});
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2014-2014 52°North Initiative for Geospatial Open Source
 * Software GmbH
 *
 * 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.
 */
var Settings = {
    // For more informations about the settings options, please check: http://52north.github.io/js-sensorweb-client
    // The entries in this list will be removed from the provider list offered to the user
    providerBlackList: [
        {
            serviceID: 'srv_6d9ccea8d609ecb74d4a512922bb7cee', // ircel
            apiUrl: 'http://sensorweb.demo.52north.org/sensorwebclient-webapp-stable/api/v1/'
        },
        {
            serviceID: 'srv_7cabc8c30a85fab035c95882df6db343', // BfG sos
            apiUrl: 'http://sensorweb.demo.52north.org/sensorwebclient-webapp-stable/api/v1/'
        },
        {
            serviceID: 'srv_7cabc8c30a85fab035c95882df6db343', // Wupperverbands-SOS
            apiUrl: 'http://sensorweb.demo.52north.org/sensorwebclient-webapp-stable/api/v1/'
        }
    ],
    // A list of timeseries-API urls and an appropriate identifier to create internal timeseries ids
    restApiUrls: {
//		'http://192.168.1.135:8080/sensorwebclient-webapp/api/v1/' : 'localhost'
//		'http://localhost:8090/sensorwebclient-webapp-3.3.0-SNAPSHOT/api/v1/' : 'localhost'
        'http://sensorweb.demo.52north.org/sensorwebclient-webapp-stable/api/v1/': '52nSensorweb',
        'http://sosrest.irceline.be/api/v1/': 'irceline',
        'http://www.fluggs.de/sos2/api/v1/': 'fluggs',
        'http://sensors.geonovum.nl/sos/api/v1/': 'geonovum'
    },
    // default selected provider
    defaultProvider: {
        serviceID: 'srv_738111ed219f738cfc85be0c8d87843c',
        apiUrl: 'http://sensorweb.demo.52north.org/sensorwebclient-webapp-stable/api/v1/'
    },
    // default setting for clustering stations
    clusterStations: true,
    // default setting for generalization of the data
    generalizeData: true,
    // default setting for save status
    saveStatus: false,
    // default setting for concentration marker
    concentrationMarker: false,
    // map options of leaflet
    mapOptions: {},
    // zoom level in the map, used for user location and station position
    zoom: 13,
    // date/time format which is used on several places
    dateformat: 'DD.MM.YY HH:mmZ',
    shortDateformat: 'DD.MM.YY',
    // duration after which latest values shall be ignored when rendering marker in the map
    ignoreAfterDuration: moment.duration(1, 'y'),
    // duration buffer for time series request
    timeseriesDataBuffer: moment.duration(2, 'h'),
    // default color for circled marker, when last value is older than 'ignoreAfterDuration' or the timeseries has no last value
    defaultMarkerColor: '#123456',
    // default scaling of loaded diagram
    defaultZeroScale: false,
    // default grouping timeseries with same uom
    defaultGroupedAxis: true,
    // additional parameters which are append to the request urls
    additionalParameters: {
        locale: 'de'
    },
    // default language for i18n
    defaultLanguage: 'en',
    // should saving the status be possible,
    saveStatusPossible: true,
    // entries on a page for the values table
    pagesize: 20,
    // line width for selected timeseries
    selectedLineWidth: 5,
    // common line width for unselected timeseries
    commonLineWidth: 2,
    // chart styling options see for more details: https://github.com/flot/flot/blob/master/API.md
    chartOptions: {},
    // colorlist to select for a different timeseries color
    colorList: ['#1abc9c', '#27ae60', '#2980b9', '#8e44ad', '#2c3e50', '#f1c40f',
        '#d35400', '#c0392b', '#7f8c8d'],
    // interval to display the timeseries in a bar diagram with label and value in hours
    intervalList: [
        {label: _('styleChange.barChartInterval.hour'), value: 1},
        {label: _('styleChange.barChartInterval.day'), value: 24},
        {label: _('styleChange.barChartInterval.week'), value: 7 * 24},
        {label: _('styleChange.barChartInterval.month'), value: 30 * 24}
    ],
    timeRangeData: {
        presets: [
            {
                name: 'lastHour',
                label: _('timeSelection.presets.lastHour'),
                interval: {
                    from: moment().subtract(1, 'hours'),
                    till: moment(),
                    mode: 'minutes'
                }
            },
            {
                name: 'today',
                label: _('timeSelection.presets.today'),
                interval: {
                    from: moment().startOf('day'),
                    till: moment().endOf('day'),
                    mode: 'day'
                }
            },
            {
                name: 'yesterday',
                label: _('timeSelection.presets.yesterday'),
                interval: {
                    from: moment().subtract(1, 'days').startOf('day'),
                    till: moment().subtract(1, 'days').endOf('day'),
                    mode: 'day'
                }
            },
            {
                name: 'todayYesterday',
                label: _('timeSelection.presets.todayYesterday'),
                interval: {
                    from: moment().subtract(1, 'days').startOf('day'),
                    //till: moment(),
                    mode: 'day'
                }
            },
            {
                name: 'thisWeek',
                label: _('timeSelection.presets.thisWeek'),
                interval: {
                    from: moment().startOf('week'),
                    //till: moment(),
                    mode: 'week'
                }
            },
            {
                name: 'lastWeek',
                label: _('timeSelection.presets.lastWeek'),
                interval: {
                    from: moment().subtract(1, 'weeks').startOf('week'),
                    till: moment().subtract(1, 'weeks').endOf('week'),
                    mode: 'week'
                }
            },
            {
                name: 'thisMonth',
                label: _('timeSelection.presets.thisMonth'),
                interval: {
                    from: moment().startOf('month'),
                    //till: moment(),
                    mode: 'month'
                }
            },
            {
                name: 'lastMonth',
                label: _('timeSelection.presets.lastMonth'),
                interval: {
                    from: moment().subtract(1, 'months').startOf('month'),
                    till: moment().subtract(1, 'months').endOf('month'),
                    mode: 'month'
                }
            },
            {
                name: 'thisYear',
                label: _('timeSelection.presets.thisYear'),
                interval: {
                    from: moment().startOf('year'),
                    //till: moment(),
                    mode: 'year'
                }
            },
            {
                name: 'lastYear',
                label: _('timeSelection.presets.lastYear'),
                interval: {
                    from: moment().subtract(1, 'years').startOf('year'),
                    till: moment().subtract(1, 'years').endOf('year'),
                    mode: 'year'
                }
            }
        ]
    },
    notifyOptions: {
        position: 'bottom-left',
        fade_in_speed: 1000,
        fade_out_speed: 1000,
        time: 2000
    },
    wmsLayer: [],
    // configuration for the tile layer in the leaflet map (see for more information: http://leafletjs.com/reference.html#tilelayer )
    tileLayerUrl: 'http://{s}.tile.osm.org/{z}/{x}/{y}.png',
    tileLayerOptions: {
        attribution: '© OpenStreetMap contributors'
    },
    enableGeoSearch: true
};<commit_msg>remove timezone from time format<commit_after>/*
 * Copyright (C) 2014-2014 52°North Initiative for Geospatial Open Source
 * Software GmbH
 *
 * 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.
 */
var Settings = {
    // For more informations about the settings options, please check: http://52north.github.io/js-sensorweb-client
    // The entries in this list will be removed from the provider list offered to the user
    providerBlackList: [
        {
            serviceID: 'srv_6d9ccea8d609ecb74d4a512922bb7cee', // ircel
            apiUrl: 'http://sensorweb.demo.52north.org/sensorwebclient-webapp-stable/api/v1/'
        },
        {
            serviceID: 'srv_7cabc8c30a85fab035c95882df6db343', // BfG sos
            apiUrl: 'http://sensorweb.demo.52north.org/sensorwebclient-webapp-stable/api/v1/'
        },
        {
            serviceID: 'srv_7cabc8c30a85fab035c95882df6db343', // Wupperverbands-SOS
            apiUrl: 'http://sensorweb.demo.52north.org/sensorwebclient-webapp-stable/api/v1/'
        }
    ],
    // A list of timeseries-API urls and an appropriate identifier to create internal timeseries ids
    restApiUrls: {
//		'http://192.168.1.135:8080/sensorwebclient-webapp/api/v1/' : 'localhost'
//		'http://localhost:8090/sensorwebclient-webapp-3.3.0-SNAPSHOT/api/v1/' : 'localhost'
        'http://sensorweb.demo.52north.org/sensorwebclient-webapp-stable/api/v1/': '52nSensorweb',
        'http://sosrest.irceline.be/api/v1/': 'irceline',
        'http://www.fluggs.de/sos2/api/v1/': 'fluggs',
        'http://sensors.geonovum.nl/sos/api/v1/': 'geonovum'
    },
    // default selected provider
    defaultProvider: {
        serviceID: 'srv_738111ed219f738cfc85be0c8d87843c',
        apiUrl: 'http://sensorweb.demo.52north.org/sensorwebclient-webapp-stable/api/v1/'
    },
    // default setting for clustering stations
    clusterStations: true,
    // default setting for generalization of the data
    generalizeData: true,
    // default setting for save status
    saveStatus: false,
    // default setting for concentration marker
    concentrationMarker: false,
    // map options of leaflet
    mapOptions: {},
    // zoom level in the map, used for user location and station position
    zoom: 13,
    // date/time format which is used on several places
    dateformat: 'DD.MM.YY HH:mm',
    shortDateformat: 'DD.MM.YY',
    // duration after which latest values shall be ignored when rendering marker in the map
    ignoreAfterDuration: moment.duration(1, 'y'),
    // duration buffer for time series request
    timeseriesDataBuffer: moment.duration(2, 'h'),
    // default color for circled marker, when last value is older than 'ignoreAfterDuration' or the timeseries has no last value
    defaultMarkerColor: '#123456',
    // default scaling of loaded diagram
    defaultZeroScale: false,
    // default grouping timeseries with same uom
    defaultGroupedAxis: true,
    // additional parameters which are append to the request urls
    additionalParameters: {
        locale: 'de'
    },
    // default language for i18n
    defaultLanguage: 'en',
    // should saving the status be possible,
    saveStatusPossible: true,
    // entries on a page for the values table
    pagesize: 20,
    // line width for selected timeseries
    selectedLineWidth: 5,
    // common line width for unselected timeseries
    commonLineWidth: 2,
    // chart styling options see for more details: https://github.com/flot/flot/blob/master/API.md
    chartOptions: {},
    // colorlist to select for a different timeseries color
    colorList: ['#1abc9c', '#27ae60', '#2980b9', '#8e44ad', '#2c3e50', '#f1c40f',
        '#d35400', '#c0392b', '#7f8c8d'],
    // interval to display the timeseries in a bar diagram with label and value in hours
    intervalList: [
        {label: _('styleChange.barChartInterval.hour'), value: 1},
        {label: _('styleChange.barChartInterval.day'), value: 24},
        {label: _('styleChange.barChartInterval.week'), value: 7 * 24},
        {label: _('styleChange.barChartInterval.month'), value: 30 * 24}
    ],
    timeRangeData: {
        presets: [
            {
                name: 'lastHour',
                label: _('timeSelection.presets.lastHour'),
                interval: {
                    from: moment().subtract(1, 'hours'),
                    till: moment(),
                    mode: 'minutes'
                }
            },
            {
                name: 'today',
                label: _('timeSelection.presets.today'),
                interval: {
                    from: moment().startOf('day'),
                    till: moment().endOf('day'),
                    mode: 'day'
                }
            },
            {
                name: 'yesterday',
                label: _('timeSelection.presets.yesterday'),
                interval: {
                    from: moment().subtract(1, 'days').startOf('day'),
                    till: moment().subtract(1, 'days').endOf('day'),
                    mode: 'day'
                }
            },
            {
                name: 'todayYesterday',
                label: _('timeSelection.presets.todayYesterday'),
                interval: {
                    from: moment().subtract(1, 'days').startOf('day'),
                    //till: moment(),
                    mode: 'day'
                }
            },
            {
                name: 'thisWeek',
                label: _('timeSelection.presets.thisWeek'),
                interval: {
                    from: moment().startOf('week'),
                    //till: moment(),
                    mode: 'week'
                }
            },
            {
                name: 'lastWeek',
                label: _('timeSelection.presets.lastWeek'),
                interval: {
                    from: moment().subtract(1, 'weeks').startOf('week'),
                    till: moment().subtract(1, 'weeks').endOf('week'),
                    mode: 'week'
                }
            },
            {
                name: 'thisMonth',
                label: _('timeSelection.presets.thisMonth'),
                interval: {
                    from: moment().startOf('month'),
                    //till: moment(),
                    mode: 'month'
                }
            },
            {
                name: 'lastMonth',
                label: _('timeSelection.presets.lastMonth'),
                interval: {
                    from: moment().subtract(1, 'months').startOf('month'),
                    till: moment().subtract(1, 'months').endOf('month'),
                    mode: 'month'
                }
            },
            {
                name: 'thisYear',
                label: _('timeSelection.presets.thisYear'),
                interval: {
                    from: moment().startOf('year'),
                    //till: moment(),
                    mode: 'year'
                }
            },
            {
                name: 'lastYear',
                label: _('timeSelection.presets.lastYear'),
                interval: {
                    from: moment().subtract(1, 'years').startOf('year'),
                    till: moment().subtract(1, 'years').endOf('year'),
                    mode: 'year'
                }
            }
        ]
    },
    notifyOptions: {
        position: 'bottom-left',
        fade_in_speed: 1000,
        fade_out_speed: 1000,
        time: 2000
    },
    wmsLayer: [],
    // configuration for the tile layer in the leaflet map (see for more information: http://leafletjs.com/reference.html#tilelayer )
    tileLayerUrl: 'http://{s}.tile.osm.org/{z}/{x}/{y}.png',
    tileLayerOptions: {
        attribution: '© OpenStreetMap contributors'
    },
    enableGeoSearch: true
};<|endoftext|>


<commit_before>'use strict';

function enumToName(list, val) {
  for (var i in list) {
    if (list.hasOwnProperty(i)) {
      if (list[i] === val) {
        return i;
      }
    }
  }
  return 'unknown:' + val;
}

function hrTime() {
  return (new Date()).getTime();
}

var clientParams = [];
if (window.location.hash.length > 1) {
  clientParams = window.location.hash.substr(1).split(',');
}

function debugValidateProps(obj, props) {
  for (var i = 0; i < props.length; ++i) {
    var propVal = eval('obj.' + props[i][0]);
    if (propVal === undefined) {
      console.warn('Expected property to be set: ', props[i][0]);
      console.trace();
      continue;
    }
    if (props[i][1] && propVal < props[i][1] ||
        props[i][1] && propVal > props[i][1]) {
      console.warn('Expected property', props[i][0], 'to be between', props[i][1], 'and', props[i][1], '(got:' + propVal + ')');
      console.trace();
      continue;
    }
  }
}

function normalizePath(path) {
  path = path.replace(/\\/g, '/');
  path = path.replace(/\/\//g, '/');
  return path;
}

function MultiWait() {
  this.count = 0;
  this.callback = null;

  var self = this;
  this.waitFn = function() {
    self.count--;
    if (self.count === 0 && self.callback) {
      self.callback();
    }
  };
}
MultiWait.prototype.one = function() {
  this.count++;
  return this.waitFn;
};
MultiWait.prototype.wait = function(callback) {
  if (this.count === 0) {
    if (callback) {
      callback();
    }
  } else {
    this.callback = callback;
  }
};
<commit_msg>Made debugPropertyValidate helper work with int64 numbers.<commit_after>'use strict';

function enumToName(list, val) {
  for (var i in list) {
    if (list.hasOwnProperty(i)) {
      if (list[i] === val) {
        return i;
      }
    }
  }
  return 'unknown:' + val;
}

function hrTime() {
  return (new Date()).getTime();
}

var clientParams = [];
if (window.location.hash.length > 1) {
  clientParams = window.location.hash.substr(1).split(',');
}

function debugValidateProps(obj, props) {
  for (var i = 0; i < props.length; ++i) {
    var propVal = eval('obj.' + props[i][0]);
    if (propVal === undefined) {
      console.warn('Expected property to be set: ', props[i][0]);
      console.trace();
      continue;
    }
    if (propVal instanceof Int64) {
      // We can cheat since the check value has to be 32bit anyways.
      if (propVal.hi !== 0 ||
        props[i][1] && propVal.lo < props[i][1] ||
        props[i][1] && propVal.lo > props[i][1]) {
        console.warn('Expected property', props[i][0], 'to be between', props[i][1], 'and', props[i][1], '(got:0x' + propVal.toString(16) + ')');
        console.trace();
        continue;
      }
    } else {
      if (props[i][1] && propVal < props[i][1] ||
          props[i][1] && propVal > props[i][1]) {
        console.warn('Expected property', props[i][0], 'to be between', props[i][1], 'and', props[i][1], '(got:' + propVal + ')');
        console.trace();
        continue;
      }
    }
  }
}

function normalizePath(path) {
  path = path.replace(/\\/g, '/');
  path = path.replace(/\/\//g, '/');
  return path;
}

function MultiWait() {
  this.count = 0;
  this.callback = null;

  var self = this;
  this.waitFn = function() {
    self.count--;
    if (self.count === 0 && self.callback) {
      self.callback();
    }
  };
}
MultiWait.prototype.one = function() {
  this.count++;
  return this.waitFn;
};
MultiWait.prototype.wait = function(callback) {
  if (this.count === 0) {
    if (callback) {
      callback();
    }
  } else {
    this.callback = callback;
  }
};
<|endoftext|>


<commit_before>'use strict';

const cellStates = {
	x: 'x',
	o: 'o',
	_: '_'
};

const Box = React.createClass({
	handlerOnClick: function (x, y) {
		console.log(x, y);
	},
	getInitialState: function () {
		return {
			size: 3,
			matrix: [
				[cellStates._, cellStates._, cellStates._],
				[cellStates._, cellStates._, cellStates._],
				[cellStates._, cellStates._, cellStates._]
			]
		};
	},
	changeStateOfCell: function (x, y) {
		var matrix = this.state.matrix;
		matrix[x][y] = cellStates.x;
		this.setState({matrix: matrix});
	},
	render: function () {
		const matrix = this.state.matrix;
		return (

						{matrix.map(function(row, index){
							return <Row cells={row} changeStateOfCell={this.changeStateOfCell} rowindex={index} />;
						}, this)}

			);
	}
});

const Row = React.createClass({
	rowCellOnClick: function (x, y) {
		this.props.changeStateOfCell(x, y);
	},
	render: function () {
		const cells = this.props.cells;
		const rowIndex = this.props.rowindex;
		return (

				{cells.map(function(cell, index){
					return <Cell item={cell} rowCellOnClick={this.rowCellOnClick} rowindex={rowIndex} cellindex={index} />;
				}, this)}

);
	}
});

const Cell = React.createClass({
	handlerOnClick: function () {
		const x = this.props.rowindex;
		const y = this.props.cellindex;
		this.props.rowCellOnClick(x, y);
	},
	render: function () {
		const item = this.props.item;
		return (| {item} |	);
	}
});

ReactDOM.render(
  <Box />,
  document.getElementById('xo')
);
<commit_msg>add CHANGE PLAYER button<commit_after>'use strict';

const cellStates = {
	x: 'x',
	o: 'o',
	_: '_'
};

const Box = React.createClass({
	getInitialState: function () {
		return {
			size: 3,
			player: cellStates.x,
			matrix: [
				[cellStates._, cellStates._, cellStates._],
				[cellStates._, cellStates._, cellStates._],
				[cellStates._, cellStates._, cellStates._]
			]
		};
	},
	changePlayer: function () {
		const player = (this.state.player === cellStates.x)? cellStates.o : cellStates.x;
		this.setState({player: player});
	},
	changeStateOfCell: function (x, y) {
		const playerSimbol = (this.state.player == cellStates.x) ? cellStates.x : cellStates.o;
		var matrix = this.state.matrix;
		matrix[x][y] = playerSimbol;
		this.setState({matrix: matrix});
	},
	render: function () {
		const matrix = this.state.matrix;
		return (

						{matrix.map(function(row, index){
							return <Row cells={row} changeStateOfCell={this.changeStateOfCell} rowindex={index} />;
						}, this)}

				<Player changePlayer={this.changePlayer} player={this.state.player} />

			);
	}
});

const Row = React.createClass({
	rowCellOnClick: function (x, y) {
		this.props.changeStateOfCell(x, y);
	},
	render: function () {
		const cells = this.props.cells;
		const rowIndex = this.props.rowindex;
		return (

				{cells.map(function(cell, index){
					return <Cell item={cell} rowCellOnClick={this.rowCellOnClick} rowindex={rowIndex} cellindex={index} />;
				}, this)}

);
	}
});

const Cell = React.createClass({
	handlerOnClick: function () {
		const x = this.props.rowindex;
		const y = this.props.cellindex;
		this.props.rowCellOnClick(x, y);
	},
	render: function () {
		const item = this.props.item;
		return (| {item} |	);
	}
});

const Player = React.createClass({
	changePlayer: function () {
		this.props.changePlayer();
	},
	render: function () {
		const player = this.props.player;
		return (

Player: {player}

			<button onClick={this.changePlayer}>Change Player</ button>

);
	}
});

ReactDOM.render(
	<Box />,
  	document.getElementById('xo')
);
<|endoftext|>


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

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { STORE_NAME as CORE_SITE } from '../datastore/site/constants';
import { STORE_NAME as CORE_USER } from '../datastore/user/constants';

const { createRegistrySelector } = Data;

/**
 * Creates a store object that has selectors for managing site info.
 *
 * @since n.e.x.t
 * @private
 *
 * @param {string}  slug                  Slug of the module that the store is for.
 * @param {Object}  options               Options to consider for the store.
 * @param {number}  options.storeName     Store name to use.
 * @param {string}  options.adminPage     Store admin page. Default is 'googlesitekit-dashboard'.
 * @param {boolean} options.requiresSetup Store flag, for requires setup. Default is 'true'
 * @return {Object} The info store object.
 */
export const createInfoStore = ( slug, {
	storeName = undefined,
	adminPage = 'googlesitekit-dashboard',
	requiresSetup = true,
} = {} ) => {
	const STORE_NAME = storeName || `modules/${ slug }`;

	const INITIAL_STATE = {};
	const actions = {};
	const controls = {};
	const reducer = ( state ) => {
		return { ...state };
	};
	const resolvers = {};
	const selectors = {
		/**
		 * Returns admin screen URL.
		 *
		 * @since n.e.x.t
		 *
		 * @param {(Object|undefined)} queryArgs Query arguments to add to admin URL.
		 * @return {(string|undefined)} The admin screen URL.
		 */
		getAdminScreenURL: createRegistrySelector( ( select ) => ( queryArgs ) => {
			return select( CORE_SITE ).getAdminURL( adminPage, queryArgs );
		} ),

		/**
		 * Returns admin reauthentication URL.
		 *
		 * @since n.e.x.t
		 *
		 * @param {boolean} reAuth The module activation status. Default is true.
		 * @return {(string|undefined)} The admin reauthentication URL.
		 */
		getAdminReauthURL: createRegistrySelector( ( select ) => ( reAuth = true ) => {
			const { needsReauthentication } = select( CORE_USER ).getAuthentication() || {};

			const noSetupQueryArgs = ! requiresSetup ? {
				notification: 'authentication_success',
				reAuth: undefined,
			} : {};

			if ( ! needsReauthentication ) {
				return select( STORE_NAME ).getAdminScreenURL( { slug, reAuth, ...noSetupQueryArgs } );
			}

			return select( STORE_NAME ).getAdminScreenURL( { slug, reAuth } );
		} ),
	};

	return {
		STORE_NAME,
		INITIAL_STATE,
		actions,
		controls,
		reducer,
		resolvers,
		selectors,
	};
};
<commit_msg>Refactored selector getadminreauthurl to add connecturl.<commit_after>/**
 * core/modules data store: info.
 *
 * Site Kit by Google, Copyright 2020 Google LLC
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * WordPress dependencies
 */
import { addQueryArgs } from '@wordpress/url';

/**
 * Internal dependencies
 */
import Data from 'googlesitekit-data';
import { STORE_NAME as CORE_SITE } from '../datastore/site/constants';
import { STORE_NAME as CORE_USER } from '../datastore/user/constants';

const { createRegistrySelector } = Data;

/**
 * Creates a store object that has selectors for managing site info.
 *
 * @since n.e.x.t
 * @private
 *
 * @param {string}  slug                  Slug of the module that the store is for.
 * @param {Object}  options               Options to consider for the store.
 * @param {number}  options.storeName     Store name to use.
 * @param {string}  options.adminPage     Store admin page. Default is 'googlesitekit-dashboard'.
 * @param {boolean} options.requiresSetup Store flag, for requires setup. Default is 'true'
 * @return {Object} The info store object.
 */
export const createInfoStore = ( slug, {
	storeName = undefined,
	adminPage = 'googlesitekit-dashboard',
	requiresSetup = true,
} = {} ) => {
	const STORE_NAME = storeName || `modules/${ slug }`;

	const INITIAL_STATE = {};
	const actions = {};
	const controls = {};
	const reducer = ( state ) => {
		return { ...state };
	};
	const resolvers = {};
	const selectors = {
		/**
		 * Returns admin screen URL.
		 *
		 * @since n.e.x.t
		 *
		 * @param {(Object|undefined)} queryArgs Query arguments to add to admin URL.
		 * @return {(string|undefined)} The admin screen URL.
		 */
		getAdminScreenURL: createRegistrySelector( ( select ) => ( queryArgs ) => {
			return select( CORE_SITE ).getAdminURL( adminPage, queryArgs );
		} ),

		/**
		 * Returns admin reauthentication URL.
		 *
		 * @since n.e.x.t
		 *
		 * @param {boolean} reAuth The module activation status. Default is true.
		 * @return {(string|undefined)} The admin reauthentication URL.
		 */
		getAdminReauthURL: createRegistrySelector( ( select ) => ( reAuth = true ) => {
			const { needsReauthentication } = select( CORE_USER ).getAuthentication() || {};

			const noSetupQueryArgs = ! requiresSetup ? {
				notification: 'authentication_success',
				reAuth: undefined,
			} : {};

			const redirectURL = select( STORE_NAME ).getAdminScreenURL( { slug, reAuth, ...noSetupQueryArgs } );

			if ( ! needsReauthentication ) {
				return redirectURL;
			}

			const connectURL = select( CORE_USER ).getConnectURL( { redirectURL } );

			return addQueryArgs( connectURL, { status: reAuth } );
		} ),
	};

	return {
		STORE_NAME,
		INITIAL_STATE,
		actions,
		controls,
		reducer,
		resolvers,
		selectors,
	};
};
<|endoftext|>


<commit_before>/*global QUnit, sinon*/

sap.ui.define([
	"sap/ui/core/ComponentContainer",
	"sap/m/Shell",
	"sap/ui/core/Core",
	"sap/uxap/BlockBase",
	"sap/uxap/ObjectPageLayout",
	"sap/uxap/ObjectPageSection",
	"sap/uxap/ObjectPageSubSection",
	"sap/ui/core/mvc/XMLView"],
function (ComponentContainer, Shell, Core, BlockBase, ObjectPageLayout, ObjectPageSection, ObjectPageSubSection, XMLView) {
	"use strict";

	QUnit.module("BlockBase");

	QUnit.test("Owner component propagated to views", function (assert) {
		var oComponentContainer,
			oComponent,
			oMainView,
			oMainController,
			oObjectPage,
			oBlock,
			oBlockView,
			oBlockViewController,
			done = assert.async();

		assert.expect(5);

		new Shell("Shell", {
			app: new ComponentContainer("myComponentContainer", {
				name: 'blockbasetest',
				height: "100%"
			})
		}).placeAt('qunit-fixture');

		Core.applyChanges();

		setTimeout(function () {
			oComponentContainer = Core.byId("myComponentContainer");
			oComponent = oComponentContainer.getComponentInstance();

			assert.ok(oComponent && oComponent.getMetadata().getName() === "blockbasetest.Component", "The component was successfully created");

			oMainView = oComponent.getRootControl();
			oMainController = oMainView.getController();

			assert.ok(oMainView && oMainController, "The main view and controller were successfully created");

			oObjectPage = oMainView.byId("ObjectPageLayout");
			oBlock = oObjectPage.getSections()[0].getSubSections()[0].getBlocks()[0];

			assert.ok(oBlock, "The block was successfully created");
			oBlockView = oBlock.getAggregation("_views")[0];
			oBlockViewController = oBlockView.getController();

			assert.ok(oBlockView && oBlockViewController, "The block view and its controller were successfully created");

			assert.strictEqual(oBlockViewController.getOwnerComponent(), oMainController.getOwnerComponent(), "The block view is owned by the component");

			done();
		}, 200);
	});

	QUnit.test("blocks are target of lazy loading feature", function (assert) {

		var fnGetBlock = function() {
			return new BlockBase();
		},
		aBlocksOutsideSubSection = [fnGetBlock(), fnGetBlock(), fnGetBlock()],
		aBlocksInsideSubSection = [fnGetBlock(), fnGetBlock(), fnGetBlock()],
		oObjectPageLayout = new ObjectPageLayout({
			enableLazyLoading: true,
			headerContent: aBlocksOutsideSubSection,
			sections:[
					new ObjectPageSection({

						subSections: [
							new ObjectPageSubSection({
								blocks: aBlocksInsideSubSection
							})
						]
				})
			]
		}),
		fnAssertShouldBeLoadedLazily = function(assert, oBlock, bExpected) {
			var bShouldLazyLoad = oBlock._shouldLazyLoad();
			assert.strictEqual(bShouldLazyLoad, bExpected, " The block " + oBlock.getId() + " is target of lazy loading " + bShouldLazyLoad);
		};

		// Assert: the blocks ObejctPageSubSection are target of lazy laoding
		aBlocksInsideSubSection.forEach(function(oBlock) {
			fnAssertShouldBeLoadedLazily(assert, oBlock, true);
		});

		// Assert: the blocks outside the ObejctPageSubSection are not target of lazy laoding
		aBlocksOutsideSubSection.forEach(function(oBlock) {
			fnAssertShouldBeLoadedLazily(assert, oBlock, false);
		});

		oObjectPageLayout.destroy();
	});

	QUnit.module("BlockBase Height", {

		beforeEach: function (assert) {
			var done = assert.async();
			XMLView.create({
				id: "UxAP-InfoBlocks",
				viewName: "view.UxAP-InfoBlocks"
			}).then(function (oView) {
				this.oObjectPageInfoView = oView;
				this.oObjectPageInfoView.placeAt('qunit-fixture');
				Core.applyChanges();
				done();
			}.bind(this));
		},
		afterEach: function () {
			this.oObjectPageInfoView.destroy();
		}
	});

	QUnit.test("ObjectPage blocks height", function (assert) {

		var oOPL = this.oObjectPageInfoView.byId("ObjectPageLayout"),
			oTargetSubSection = oOPL.getSections()[0].getSubSections()[4],
			iTargetScrollTop,
			iActualScrollTop,
			done = assert.async();

		assert.expect(1);

		oOPL.attachEventOnce("onAfterRenderingDOMReady", function () {

			// Act: scroll to target section, that will result in:
			// (1) will change the scrollTop [to the one that brings the target section to the top of sections container]
			// (2) will trigger lazy loading of the target section
			oOPL.scrollToSection(oTargetSubSection.getId());

			// Act: as the above call is asynchronous (but returns no promise),
			// make the test synchronous by *explicitly* calling the actions for (1) and (2) above
			// (1) explicitly change the scrollTop to target position [to the one that brings the target section to the top of sections container]
			iTargetScrollTop = oOPL._computeScrollPosition(oTargetSubSection);
			oOPL._$opWrapper.scrollTop(iTargetScrollTop);
			// (2) explicitly connect the section models (lazy loading)
			oOPL._connectModelsForSections([oTargetSubSection]);
			Core.applyChanges();

			// Check if the scrollTop [of the lazy-loaded section] matches the expected one
			iActualScrollTop = Math.ceil(oOPL._$opWrapper.scrollTop());
			assert.ok(iTargetScrollTop === iActualScrollTop || iTargetScrollTop === iActualScrollTop + 1, "scrollTop of lazy-loaded section not did not change upon lazy-loading");
			done();
		});
	});

	QUnit.module("View selection", {

		beforeEach: function (assert) {
			var done = assert.async();
			XMLView.create({
				id: "UxAP-InfoBlocks",
				viewName: "view.UxAP-InfoBlocks"
			}).then(function (oView) {
				this.oObjectPageInfoView = oView;
				this.oObjectPageInfoView.placeAt('qunit-fixture');
				Core.applyChanges();
				done();
			}.bind(this));
			this.sandbox = sinon.sandbox.create();
		},
		afterEach: function () {
			this.oObjectPageInfoView.destroy();
			this.sandbox.restore();
		}
	});

	QUnit.test("initView event is fired", function (assert) {

		var oOPL = this.oObjectPageInfoView.byId("ObjectPageLayout"),
			oTargetSubSection = oOPL.getSections()[0].getSubSections()[0],
			oBlock = oTargetSubSection.getBlocks()[0],
			done = assert.async();

		assert.expect(2);

		oOPL.attachEventOnce("onAfterRenderingDOMReady", function () {
			oBlock._selectView("Expanded");
			oBlock.attachEvent("viewInit", function(oEvent) {
				var oView = oEvent.getParameter("view");
				assert.ok(oView, "event is fired");
				assert.strictEqual(oView.getViewName(), "sap.uxap.testblocks.objectpageblock.InfoButtonExpanded");
				done();
			});
		});

	});

	QUnit.test("view is created only once", function (assert) {

		var oOPL = this.oObjectPageInfoView.byId("ObjectPageLayout"),
			oTargetSubSection = oOPL.getSections()[0].getSubSections()[0],
			oBlock = oTargetSubSection.getBlocks()[0],
			oExpandViewMetadata = oBlock.getMetadata().getView("Expanded"),
			createSpy = this.sandbox.spy(sap.ui.core.mvc.View, "create"),
			done = assert.async();

		oExpandViewMetadata.id = oBlock.getId() + "-Expanded"; // setup

		assert.expect(1);

		oOPL.attachEventOnce("onAfterRenderingDOMReady", function () {

			// Setup
			createSpy.reset();

			// Act: request create the same view more than once
			oBlock.createView(oExpandViewMetadata);
			oBlock.createView(oExpandViewMetadata); // request the same view twice

			// Check
			oBlock.attachEventOnce("viewInit", function () {
				assert.ok(createSpy.calledOnce, "creation is called once only");
				done();
			});
		});
	});

	QUnit.test("notification for view selection only once", function (assert) {

		var oOPL = this.oObjectPageInfoView.byId("ObjectPageLayout"),
			oTargetSubSection = oOPL.getSections()[0].getSubSections()[0],
			oBlock = oTargetSubSection.getBlocks()[0],
			notifySpy = this.sandbox.spy(oBlock, "_notifyForLoadingInMode"),
			done = assert.async();

		assert.expect(1);

		oOPL.attachEventOnce("onAfterRenderingDOMReady", function () {

			// Setup
			notifySpy.reset();

			// Act: request select the same view more than once
			oBlock._selectView("Expanded");
			oBlock._selectView("Expanded"); // request the same view twice

			// Check
			oBlock.attachEventOnce("viewInit", function () {
				setTimeout(function() {
					assert.ok(notifySpy.calledOnce, "notification is called once only");
					done();
				}, 0);
			});
		});
	});

});
<commit_msg>[INTERNAL] sap.uxap.BlockBase: Improved test stability<commit_after>/*global QUnit, sinon*/

sap.ui.define([
	"sap/ui/core/ComponentContainer",
	"sap/m/Shell",
	"sap/ui/core/Core",
	"sap/uxap/BlockBase",
	"sap/uxap/ObjectPageLayout",
	"sap/uxap/ObjectPageSection",
	"sap/uxap/ObjectPageSubSection",
	"sap/ui/core/mvc/XMLView"],
function (ComponentContainer, Shell, Core, BlockBase, ObjectPageLayout, ObjectPageSection, ObjectPageSubSection, XMLView) {
	"use strict";

	QUnit.module("BlockBase");

	QUnit.test("Owner component propagated to views", function (assert) {
		var oComponentContainer,
			oComponent,
			oMainView,
			oMainController,
			oObjectPage,
			oBlock,
			oBlockView,
			oBlockViewController,
			done = assert.async();

		assert.expect(5);

		new Shell("Shell", {
			app: new ComponentContainer("myComponentContainer", {
				name: 'blockbasetest',
				height: "100%"
			})
		}).placeAt('qunit-fixture');

		Core.applyChanges();

		setTimeout(function () {
			oComponentContainer = Core.byId("myComponentContainer");
			oComponent = oComponentContainer.getComponentInstance();

			assert.ok(oComponent && oComponent.getMetadata().getName() === "blockbasetest.Component", "The component was successfully created");

			oMainView = oComponent.getRootControl();
			oMainController = oMainView.getController();

			assert.ok(oMainView && oMainController, "The main view and controller were successfully created");

			oObjectPage = oMainView.byId("ObjectPageLayout");
			oBlock = oObjectPage.getSections()[0].getSubSections()[0].getBlocks()[0];

			assert.ok(oBlock, "The block was successfully created");
			oBlockView = oBlock.getAggregation("_views")[0];
			oBlockViewController = oBlockView.getController();

			assert.ok(oBlockView && oBlockViewController, "The block view and its controller were successfully created");

			assert.strictEqual(oBlockViewController.getOwnerComponent(), oMainController.getOwnerComponent(), "The block view is owned by the component");

			done();
		}, 1000);
	});

	QUnit.test("blocks are target of lazy loading feature", function (assert) {

		var fnGetBlock = function() {
			return new BlockBase();
		},
		aBlocksOutsideSubSection = [fnGetBlock(), fnGetBlock(), fnGetBlock()],
		aBlocksInsideSubSection = [fnGetBlock(), fnGetBlock(), fnGetBlock()],
		oObjectPageLayout = new ObjectPageLayout({
			enableLazyLoading: true,
			headerContent: aBlocksOutsideSubSection,
			sections:[
					new ObjectPageSection({

						subSections: [
							new ObjectPageSubSection({
								blocks: aBlocksInsideSubSection
							})
						]
				})
			]
		}),
		fnAssertShouldBeLoadedLazily = function(assert, oBlock, bExpected) {
			var bShouldLazyLoad = oBlock._shouldLazyLoad();
			assert.strictEqual(bShouldLazyLoad, bExpected, " The block " + oBlock.getId() + " is target of lazy loading " + bShouldLazyLoad);
		};

		// Assert: the blocks ObejctPageSubSection are target of lazy laoding
		aBlocksInsideSubSection.forEach(function(oBlock) {
			fnAssertShouldBeLoadedLazily(assert, oBlock, true);
		});

		// Assert: the blocks outside the ObejctPageSubSection are not target of lazy laoding
		aBlocksOutsideSubSection.forEach(function(oBlock) {
			fnAssertShouldBeLoadedLazily(assert, oBlock, false);
		});

		oObjectPageLayout.destroy();
	});

	QUnit.module("BlockBase Height", {

		beforeEach: function (assert) {
			var done = assert.async();
			XMLView.create({
				id: "UxAP-InfoBlocks",
				viewName: "view.UxAP-InfoBlocks"
			}).then(function (oView) {
				this.oObjectPageInfoView = oView;
				this.oObjectPageInfoView.placeAt('qunit-fixture');
				Core.applyChanges();
				done();
			}.bind(this));
		},
		afterEach: function () {
			this.oObjectPageInfoView.destroy();
		}
	});

	QUnit.test("ObjectPage blocks height", function (assert) {

		var oOPL = this.oObjectPageInfoView.byId("ObjectPageLayout"),
			oTargetSubSection = oOPL.getSections()[0].getSubSections()[4],
			iTargetScrollTop,
			iActualScrollTop,
			done = assert.async();

		assert.expect(1);

		oOPL.attachEventOnce("onAfterRenderingDOMReady", function () {

			// Act: scroll to target section, that will result in:
			// (1) will change the scrollTop [to the one that brings the target section to the top of sections container]
			// (2) will trigger lazy loading of the target section
			oOPL.scrollToSection(oTargetSubSection.getId());

			// Act: as the above call is asynchronous (but returns no promise),
			// make the test synchronous by *explicitly* calling the actions for (1) and (2) above
			// (1) explicitly change the scrollTop to target position [to the one that brings the target section to the top of sections container]
			iTargetScrollTop = oOPL._computeScrollPosition(oTargetSubSection);
			oOPL._$opWrapper.scrollTop(iTargetScrollTop);
			// (2) explicitly connect the section models (lazy loading)
			oOPL._connectModelsForSections([oTargetSubSection]);
			Core.applyChanges();

			// Check if the scrollTop [of the lazy-loaded section] matches the expected one
			iActualScrollTop = Math.ceil(oOPL._$opWrapper.scrollTop());
			assert.ok(iTargetScrollTop === iActualScrollTop || iTargetScrollTop === iActualScrollTop + 1, "scrollTop of lazy-loaded section not did not change upon lazy-loading");
			done();
		});
	});

	QUnit.module("View selection", {

		beforeEach: function (assert) {
			var done = assert.async();
			XMLView.create({
				id: "UxAP-InfoBlocks",
				viewName: "view.UxAP-InfoBlocks"
			}).then(function (oView) {
				this.oObjectPageInfoView = oView;
				this.oObjectPageInfoView.placeAt('qunit-fixture');
				Core.applyChanges();
				done();
			}.bind(this));
			this.sandbox = sinon.sandbox.create();
		},
		afterEach: function () {
			this.oObjectPageInfoView.destroy();
			this.sandbox.restore();
		}
	});

	QUnit.test("initView event is fired", function (assert) {

		var oOPL = this.oObjectPageInfoView.byId("ObjectPageLayout"),
			oTargetSubSection = oOPL.getSections()[0].getSubSections()[0],
			oBlock = oTargetSubSection.getBlocks()[0],
			done = assert.async();

		assert.expect(2);

		oOPL.attachEventOnce("onAfterRenderingDOMReady", function () {
			oBlock._selectView("Expanded");
			oBlock.attachEvent("viewInit", function(oEvent) {
				var oView = oEvent.getParameter("view");
				assert.ok(oView, "event is fired");
				assert.strictEqual(oView.getViewName(), "sap.uxap.testblocks.objectpageblock.InfoButtonExpanded");
				done();
			});
		});

	});

	QUnit.test("view is created only once", function (assert) {

		var oOPL = this.oObjectPageInfoView.byId("ObjectPageLayout"),
			oTargetSubSection = oOPL.getSections()[0].getSubSections()[0],
			oBlock = oTargetSubSection.getBlocks()[0],
			oExpandViewMetadata = oBlock.getMetadata().getView("Expanded"),
			createSpy = this.sandbox.spy(sap.ui.core.mvc.View, "create"),
			done = assert.async();

		oExpandViewMetadata.id = oBlock.getId() + "-Expanded"; // setup

		assert.expect(1);

		oOPL.attachEventOnce("onAfterRenderingDOMReady", function () {

			// Setup
			createSpy.reset();

			// Act: request create the same view more than once
			oBlock.createView(oExpandViewMetadata);
			oBlock.createView(oExpandViewMetadata); // request the same view twice

			// Check
			oBlock.attachEventOnce("viewInit", function () {
				assert.ok(createSpy.calledOnce, "creation is called once only");
				done();
			});
		});
	});

	QUnit.test("notification for view selection only once", function (assert) {

		var oOPL = this.oObjectPageInfoView.byId("ObjectPageLayout"),
			oTargetSubSection = oOPL.getSections()[0].getSubSections()[0],
			oBlock = oTargetSubSection.getBlocks()[0],
			notifySpy = this.sandbox.spy(oBlock, "_notifyForLoadingInMode"),
			done = assert.async();

		assert.expect(1);

		oOPL.attachEventOnce("onAfterRenderingDOMReady", function () {

			// Setup
			notifySpy.reset();

			// Act: request select the same view more than once
			oBlock._selectView("Expanded");
			oBlock._selectView("Expanded"); // request the same view twice

			// Check
			oBlock.attachEventOnce("viewInit", function () {
				setTimeout(function() {
					assert.ok(notifySpy.calledOnce, "notification is called once only");
					done();
				}, 0);
			});
		});
	});

});
<|endoftext|>