<reponame>AboorvaDevarajan/dash-core-components
# -*- coding: utf-8 -*-
import os
import sys
from multiprocessing import Lock
import time
import json
import re
import dash
from dash.dependencies import Input, Output, State
import dash_html_components as html
import dash_core_components as dcc
from dash.exceptions import PreventUpdate
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

try:
    from urlparse import urlparse
except ImportError:
    from urllib.parse import urlparse

from .IntegrationTests import IntegrationTests

TIMEOUT = 10


class Test1(IntegrationTests):
    def setUp(self):
        pass

    def wait_for_element_by_css_selector(self, selector):
        return WebDriverWait(self.driver, TIMEOUT).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, selector))
        )

    def wait_for_text_to_equal(self, selector, assertion_text):
        def text_equal(driver):
            text = driver.find_element_by_css_selector(selector).text
            return text == assertion_text

        WebDriverWait(self.driver, TIMEOUT).until(text_equal)

    def snapshot(self, name):
        if "PERCY_PROJECT" in os.environ and "PERCY_TOKEN" in os.environ:
            python_version = sys.version.split(" ")[0]
            print("Percy Snapshot {}".format(python_version))
            self.percy_runner.snapshot(name=name)

    def test_loading_component_initialization(self):
        lock = Lock()

        app = dash.Dash(__name__)

        app.layout = html.Div([
            dcc.Loading([
                html.Div(id='div-1')
            ], className='loading')
        ], id='root')

        @app.callback(
            Output('div-1', 'children'),
            [Input('root', 'n_clicks')]
        )
        def updateDiv(children):
            with lock:
                return 'content'

        with lock:
            self.startServer(app)
            self.wait_for_element_by_css_selector(
                '.loading .dash-spinner'
            )

        self.wait_for_element_by_css_selector(
            '.loading #div-1'
        )

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_loading_component_action(self):
        lock = Lock()

        app = dash.Dash(__name__)

        app.layout = html.Div([
            dcc.Loading([
                html.Div(id='div-1')
            ], className='loading')
        ], id='root')

        @app.callback(
            Output('div-1', 'children'),
            [Input('root', 'n_clicks')]
        )
        def updateDiv(n_clicks):
            if n_clicks is not None:
                with lock:
                    return

            return 'content'

        with lock:
            self.startServer(app)
            self.wait_for_element_by_css_selector(
                '.loading #div-1'
            )

            self.driver.find_element_by_id('root').click()

            self.wait_for_element_by_css_selector(
                '.loading .dash-spinner'
            )

        self.wait_for_element_by_css_selector(
            '.loading #div-1'
        )

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_multiple_loading_components(self):
        lock = Lock()

        app = dash.Dash(__name__)

        app.layout = html.Div([
            dcc.Loading([
                html.Button(id='btn-1')
            ], className='loading-1'),
            dcc.Loading([
                html.Button(id='btn-2')
            ], className='loading-2')
        ], id='root')

        @app.callback(
            Output('btn-1', 'value'),
            [Input('btn-2', 'n_clicks')]
        )
        def updateDiv(n_clicks):
            if n_clicks is not None:
                with lock:
                    return

            return 'content'

        @app.callback(
            Output('btn-2', 'value'),
            [Input('btn-1', 'n_clicks')]
        )
        def updateDiv(n_clicks):
            if n_clicks is not None:
                with lock:
                    return

            return 'content'

        self.startServer(app)

        self.wait_for_element_by_css_selector(
            '.loading-1 #btn-1'
        )
        self.wait_for_element_by_css_selector(
            '.loading-2 #btn-2'
        )

        with lock:
            self.driver.find_element_by_id('btn-1').click()

            self.wait_for_element_by_css_selector(
                '.loading-2 .dash-spinner'
            )
            self.wait_for_element_by_css_selector(
                '.loading-1 #btn-1'
            )

        self.wait_for_element_by_css_selector(
            '.loading-2 #btn-2'
        )

        with lock:
            self.driver.find_element_by_id('btn-2').click()

            self.wait_for_element_by_css_selector(
                '.loading-1 .dash-spinner'
            )

        self.wait_for_element_by_css_selector(
            '.loading-1 #btn-1'
        )
        self.wait_for_element_by_css_selector(
            '.loading-2 #btn-2'
        )

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_nested_loading_components(self):
        lock = Lock()

        app = dash.Dash(__name__)

        app.layout = html.Div([
            dcc.Loading([
                html.Button(id='btn-1'),
                dcc.Loading([
                    html.Button(id='btn-2')
                ], className='loading-2')
            ], className='loading-1')
        ], id='root')

        @app.callback(
            Output('btn-1', 'value'),
            [Input('btn-2', 'n_clicks')]
        )
        def updateDiv(n_clicks):
            if n_clicks is not None:
                with lock:
                    return

            return 'content'

        @app.callback(
            Output('btn-2', 'value'),
            [Input('btn-1', 'n_clicks')]
        )
        def updateDiv(n_clicks):
            if n_clicks is not None:
                with lock:
                    return

            return 'content'

        self.startServer(app)

        self.wait_for_element_by_css_selector(
            '.loading-1 #btn-1'
        )
        self.wait_for_element_by_css_selector(
            '.loading-2 #btn-2'
        )

        with lock:
            self.driver.find_element_by_id('btn-1').click()

            self.wait_for_element_by_css_selector(
                '.loading-2 .dash-spinner'
            )
            self.wait_for_element_by_css_selector(
                '.loading-1 #btn-1'
            )

        self.wait_for_element_by_css_selector(
            '.loading-2 #btn-2'
        )

        with lock:
            self.driver.find_element_by_id('btn-2').click()

            self.wait_for_element_by_css_selector(
                '.loading-1 .dash-spinner'
            )

        self.wait_for_element_by_css_selector(
            '.loading-1 #btn-1'
        )
        self.wait_for_element_by_css_selector(
            '.loading-2 #btn-2'
        )

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_dynamic_loading_component(self):
        lock = Lock()

        app = dash.Dash(__name__)
        app.config['suppress_callback_exceptions'] = True

        app.layout = html.Div([
            html.Button(id='btn-1'),
            html.Div(id='div-1')
        ])

        @app.callback(
            Output('div-1', 'children'),
            [Input('btn-1', 'n_clicks')]
        )
        def updateDiv(n_clicks):
            if n_clicks is None:
                return

            with lock:
                return html.Div([
                    html.Button(id='btn-2'),
                    dcc.Loading([
                        html.Button(id='btn-3')
                    ], className='loading-1')
                ])

        @app.callback(
            Output('btn-3', 'content'),
            [Input('btn-2', 'n_clicks')]
        )
        def updateDynamic(n_clicks):
            if n_clicks is None:
                return

            with lock:
                return 'content'

        self.startServer(app)

        self.wait_for_element_by_css_selector(
            '#btn-1'
        )
        self.wait_for_element_by_css_selector(
            '#div-1'
        )

        self.driver.find_element_by_id('btn-1').click()

        self.wait_for_element_by_css_selector(
            '#div-1 #btn-2'
        )
        self.wait_for_element_by_css_selector(
            '.loading-1 #btn-3'
        )

        with lock:
            self.driver.find_element_by_id('btn-2').click()

            self.wait_for_element_by_css_selector(
                '.loading-1 .dash-spinner'
            )

        self.wait_for_element_by_css_selector(
            '#div-1 #btn-2'
        )
        self.wait_for_element_by_css_selector(
            '.loading-1 #btn-3'
        )

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_loading_slider(self):
        lock = Lock()

        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.Button(id='test-btn'),
            html.Label(id='test-div', children=['Horizontal Slider']),
            dcc.Slider(
                id='horizontal-slider',
                min=0,
                max=9,
                marks={i: 'Label {}'.format(i) if i == 1 else str(i)
                       for i in range(1, 6)},
                value=5,
            ),
        ])

        @app.callback(
            Output('horizontal-slider', 'value'),
            [Input('test-btn', 'n_clicks')]
        )
        def user_delayed_value(n_clicks):
            with lock:
                return 5

        with lock:
            self.startServer(app)

            self.wait_for_element_by_css_selector(
                '#horizontal-slider[data-dash-is-loading="true"]'
            )

        self.wait_for_element_by_css_selector(
            '#horizontal-slider:not([data-dash-is-loading="true"])'
        )

        with lock:
            self.driver.find_element_by_id('test-btn').click()

            self.wait_for_element_by_css_selector(
                '#horizontal-slider[data-dash-is-loading="true"]'
            )

        self.wait_for_element_by_css_selector(
            '#horizontal-slider:not([data-dash-is-loading="true"])'
        )

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_horizontal_slider(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.Label('Horizontal Slider'),
            dcc.Slider(
                id='horizontal-slider',
                min=0,
                max=9,
                marks={i: 'Label {}'.format(i) if i == 1 else str(i)
                       for i in range(1, 6)},
                value=5,
            ),
        ])
        self.startServer(app)

        self.wait_for_element_by_css_selector('#horizontal-slider')
        self.snapshot('horizontal slider')

        h_slider = self.driver.find_element_by_css_selector(
            '#horizontal-slider div[role="slider"]'
        )
        h_slider.click()

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_vertical_slider(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.Label('Vertical Slider'),
            dcc.Slider(
                id='vertical-slider',
                min=0,
                max=9,
                marks={i: 'Label {}'.format(i) if i == 1 else str(i)
                       for i in range(1, 6)},
                value=5,
                vertical=True,
            ),
        ], style={'height': '500px'})
        self.startServer(app)

        self.wait_for_element_by_css_selector('#vertical-slider')
        self.snapshot('vertical slider')

        v_slider = self.driver.find_element_by_css_selector(
            '#vertical-slider div[role="slider"]'
        )
        v_slider.click()

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_loading_range_slider(self):
        lock = Lock()

        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.Button(id='test-btn'),
            html.Label(id='test-div', children=['Horizontal Range Slider']),
            dcc.RangeSlider(
                id='horizontal-range-slider',
                min=0,
                max=9,
                marks={i: 'Label {}'.format(i) if i == 1 else str(i)
                       for i in range(1, 6)},
                value=[4, 6],
            ),
        ])

        @app.callback(
            Output('horizontal-range-slider', 'value'),
            [Input('test-btn', 'n_clicks')]
        )
        def delayed_value(children):
            with lock:
                return [4, 6]

        with lock:
            self.startServer(app)

            self.wait_for_element_by_css_selector(
                '#horizontal-range-slider[data-dash-is-loading="true"]'
            )

        self.wait_for_element_by_css_selector(
            '#horizontal-range-slider:not([data-dash-is-loading="true"])'
        )

        with lock:
            self.driver.find_element_by_id('test-btn').click()

            self.wait_for_element_by_css_selector(
                '#horizontal-range-slider[data-dash-is-loading="true"]'
            )

        self.wait_for_element_by_css_selector(
            '#horizontal-range-slider:not([data-dash-is-loading="true"])'
        )

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_horizontal_range_slider(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.Label('Horizontal Range Slider'),
            dcc.RangeSlider(
                id='horizontal-range-slider',
                min=0,
                max=9,
                marks={i: 'Label {}'.format(i) if i == 1 else str(i)
                       for i in range(1, 6)},
                value=[4, 6],
            ),
        ])
        self.startServer(app)

        self.wait_for_element_by_css_selector('#horizontal-range-slider')
        self.snapshot('horizontal range slider')

        h_slider_1 = self.driver.find_element_by_css_selector(
            '#horizontal-range-slider div.rc-slider-handle-1[role="slider"]'
        )
        h_slider_1.click()

        h_slider_2 = self.driver.find_element_by_css_selector(
            '#horizontal-range-slider div.rc-slider-handle-2[role="slider"]'
        )
        h_slider_2.click()

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_vertical_range_slider(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.Label('Vertical Range Slider'),
            dcc.RangeSlider(
                id='vertical-range-slider',
                min=0,
                max=9,
                marks={i: 'Label {}'.format(i) if i == 1 else str(i)
                       for i in range(1, 6)},
                value=[4, 6],
                vertical=True,
            ),
        ], style={'height': '500px'})
        self.startServer(app)

        self.wait_for_element_by_css_selector('#vertical-range-slider')
        self.snapshot('vertical range slider')

        v_slider_1 = self.driver.find_element_by_css_selector(
            '#vertical-range-slider div.rc-slider-handle-1[role="slider"]'
        )
        v_slider_1.click()

        v_slider_2 = self.driver.find_element_by_css_selector(
            '#vertical-range-slider div.rc-slider-handle-2[role="slider"]'
        )
        v_slider_2.click()

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_tabs_in_vertical_mode(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            dcc.Tabs(id="tabs", value='tab-3', children=[
                dcc.Tab(label='Tab one', value='tab-1', id='tab-1', children=[
                    html.Div('Tab One Content')
                ]),
                dcc.Tab(label='Tab two', value='tab-2', id='tab-2', children=[
                    html.Div('Tab Two Content')
                ]),
                dcc.Tab(label='Tab three', value='tab-3', id='tab-3', children=[
                    html.Div('Tab Three Content')
                ]),
            ], vertical=True),
            html.Div(id='tabs-content')
        ])

        self.startServer(app=app)
        self.wait_for_text_to_equal('#tab-3', 'Tab three')

        self.snapshot('Tabs - vertical mode')

    def test_tabs_without_children(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.H1('Dash Tabs component demo'),
            dcc.Tabs(id="tabs", value='tab-2', children=[
                dcc.Tab(label='Tab one', value='tab-1', id='tab-1'),
                dcc.Tab(label='Tab two', value='tab-2', id='tab-2'),
            ]),
            html.Div(id='tabs-content')
        ])

        @app.callback(dash.dependencies.Output('tabs-content', 'children'),
                      [dash.dependencies.Input('tabs', 'value')])
        def render_content(tab):
            if tab == 'tab-1':
                return html.Div([
                    html.H3('Test content 1')
                ], id='test-tab-1')
            elif tab == 'tab-2':
                return html.Div([
                    html.H3('Test content 2')
                ], id='test-tab-2')

        self.startServer(app=app)

        self.wait_for_text_to_equal('#tabs-content', 'Test content 2')
        self.snapshot('initial tab - tab 2')

        selected_tab = self.wait_for_element_by_css_selector('#tab-1')
        selected_tab.click()
        time.sleep(1)
        self.wait_for_text_to_equal('#tabs-content', 'Test content 1')

    def test_tabs_with_children_undefined(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.H1('Dash Tabs component demo'),
            dcc.Tabs(id="tabs", value='tab-1'),
            html.Div(id='tabs-content')
        ])

        self.startServer(app=app)

        self.wait_for_element_by_css_selector('#tabs-content')

        self.snapshot('Tabs component with children undefined')

    def test_tabs_render_without_selected(self):
        app = dash.Dash(__name__)

        data = [
            {'id': 'one', 'value': 1},
            {'id': 'two', 'value': 2},
        ]

        menu = html.Div([
            html.Div('one', id='one'),
            html.Div('two', id='two')
        ])

        tabs_one = html.Div([
            dcc.Tabs([
                dcc.Tab(dcc.Graph(id='graph-one'), label='tab-one-one'),
            ])
        ], id='tabs-one', style={'display': 'none'})

        tabs_two = html.Div([
            dcc.Tabs([
                dcc.Tab(dcc.Graph(id='graph-two'), label='tab-two-one'),
            ])
        ], id='tabs-two', style={'display': 'none'})

        app.layout = html.Div([
            menu,
            tabs_one,
            tabs_two
        ])

        for i in ('one', 'two'):

            @app.callback(Output('tabs-{}'.format(i), 'style'),
                          [Input(i, 'n_clicks')])
            def on_click(n_clicks):
                if n_clicks is None:
                    raise PreventUpdate

                if n_clicks % 2 == 1:
                    return {'display': 'block'}
                return {'display': 'none'}

            @app.callback(Output('graph-{}'.format(i), 'figure'),
                          [Input(i, 'n_clicks')])
            def on_click(n_clicks):
                if n_clicks is None:
                    raise PreventUpdate

                return {
                    'data': [
                        {
                            'x': [1, 2, 3, 4],
                            'y': [4, 3, 2, 1]
                        }
                    ],
                    'layout': {
                        'width': 700,
                        'height': 450
                    }
                }

        self.startServer(app=app)

        button_one = self.wait_for_element_by_css_selector('#one')
        button_two = self.wait_for_element_by_css_selector('#two')

        button_one.click()

        # wait for tabs to be loaded after clicking
        WebDriverWait(self.driver, 10).until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, "#graph-one .main-svg"))
        )

        time.sleep(1)
        self.snapshot("Tabs 1 rendered ")

        button_two.click()

        # wait for tabs to be loaded after clicking
        WebDriverWait(self.driver, 10).until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, "#graph-two .main-svg"))
        )

        time.sleep(1)
        self.snapshot("Tabs 2 rendered ")

        # do some extra tests while we're here
        # and have access to Graph and plotly.js
        self.check_graph_config_shape()
        self.check_plotlyjs()

    def check_plotlyjs(self):
        # find plotly.js files in the dist folder, check that there's only one
        all_dist = os.listdir(dcc.__path__[0])
        js_re = r'^plotly-(.*)\.min\.js$'
        plotlyjs_dist = [fn for fn in all_dist if re.match(js_re, fn)]

        self.assertEqual(len(plotlyjs_dist), 1)

        # check that the version matches what we see in the page
        page_version = self.driver.execute_script('return Plotly.version;')
        dist_version = re.match(js_re, plotlyjs_dist[0]).groups()[0]
        self.assertEqual(page_version, dist_version)

    def check_graph_config_shape(self):
        config_schema = self.driver.execute_script(
            'return Plotly.PlotSchema.get().config;'
        )
        with open(os.path.join(dcc.__path__[0], 'metadata.json')) as meta:
            graph_meta = json.load(meta)['src/components/Graph.react.js']
            config_prop_shape = graph_meta['props']['config']['type']['value']

        ignored_config = [
            'setBackground',
            'showSources',
            'logging',
            'globalTransforms',
            'role'
        ]

        def crawl(schema, props):
            for prop_name in props:
                self.assertIn(prop_name, schema)

            for item_name, item in schema.items():
                if item_name in ignored_config:
                    continue

                self.assertIn(item_name, props)
                if 'valType' not in item:
                    crawl(item, props[item_name]['value'])

        crawl(config_schema, config_prop_shape)

    def test_tabs_without_value(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.H1('Dash Tabs component demo'),
            dcc.Tabs(id="tabs-without-value", children=[
                dcc.Tab(label='Tab One', value='tab-1'),
                dcc.Tab(label='Tab Two', value='tab-2'),
            ]),
            html.Div(id='tabs-content')
        ])

        @app.callback(Output('tabs-content', 'children'),
                      [Input('tabs-without-value', 'value')])
        def render_content(tab):
            if tab == 'tab-1':
                return html.H3('Default selected Tab content 1')
            elif tab == 'tab-2':
                return html.H3('Tab content 2')

        self.startServer(app=app)

        self.wait_for_text_to_equal('#tabs-content', 'Default selected Tab content 1')

        self.snapshot('Tab 1 should be selected by default')

    def test_graph_does_not_resize_in_tabs(self):
        app = dash.Dash(__name__)
        app.layout = html.Div([
            html.H1('Dash Tabs component demo'),
            dcc.Tabs(id="tabs-example", value='tab-1-example', children=[
                dcc.Tab(label='Tab One', value='tab-1-example', id='tab-1'),
                dcc.Tab(label='Tab Two', value='tab-2-example', id='tab-2'),
            ]),
            html.Div(id='tabs-content-example')
        ])

        @app.callback(Output('tabs-content-example', 'children'),
                      [Input('tabs-example', 'value')])
        def render_content(tab):
            if tab == 'tab-1-example':
                return html.Div([
                    html.H3('Tab content 1'),
                    dcc.Graph(
                        id='graph-1-tabs',
                        figure={
                            'data': [{
                                'x': [1, 2, 3],
                                'y': [3, 1, 2],
                                'type': 'bar'
                            }]
                        }
                    )
                ])
            elif tab == 'tab-2-example':
                return html.Div([
                    html.H3('Tab content 2'),
                    dcc.Graph(
                        id='graph-2-tabs',
                        figure={
                            'data': [{
                                'x': [1, 2, 3],
                                'y': [5, 10, 6],
                                'type': 'bar'
                            }]
                        }
                    )
                ])
        self.startServer(app=app)

        tab_one = self.wait_for_element_by_css_selector('#tab-1')
        tab_two = self.wait_for_element_by_css_selector('#tab-2')

        WebDriverWait(self.driver, 10).until(
            EC.element_to_be_clickable((By.ID, "tab-2"))
        )

        self.snapshot("Tabs with Graph - initial (graph should not resize)")
        tab_two.click()

        # wait for Graph's internal svg to be loaded after clicking
        WebDriverWait(self.driver, 10).until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, "#graph-2-tabs .main-svg"))
        )

        self.snapshot("Tabs with Graph - clicked tab 2 (graph should not resize)")

        WebDriverWait(self.driver, 10).until(
            EC.element_to_be_clickable((By.ID, "tab-1"))
        )

        tab_one.click()

        # wait for Graph to be loaded after clicking
        WebDriverWait(self.driver, 10).until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, "#graph-1-tabs .main-svg"))
        )

        self.snapshot("Tabs with Graph - clicked tab 1 (graph should not resize)")

    def test_location_link(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.Div(id='waitfor'),
            dcc.Location(id='test-location', refresh=False),

            dcc.Link(
                html.Button('I am a clickable button'),
                id='test-link',
                href='/test/pathname'),
            dcc.Link(
                html.Button('I am a clickable hash button'),
                id='test-link-hash',
                href='#test'),
            dcc.Link(
                html.Button('I am a clickable search button'),
                id='test-link-search',
                href='?testQuery=testValue',
                refresh=False),
            html.Button('I am a magic button that updates pathname',
                        id='test-button'),
            html.A('link to click', href='/test/pathname/a', id='test-a'),
            html.A('link to click', href='#test-hash', id='test-a-hash'),
            html.A('link to click', href='?queryA=valueA', id='test-a-query'),
            html.Div(id='test-pathname', children=[]),
            html.Div(id='test-hash', children=[]),
            html.Div(id='test-search', children=[]),
        ])

        @app.callback(
            output=Output(component_id='test-pathname',
                          component_property='children'),
            inputs=[Input(component_id='test-location', component_property='pathname')])
        def update_location_on_page(pathname):
            return pathname

        @app.callback(
            output=Output(component_id='test-hash',
                          component_property='children'),
            inputs=[Input(component_id='test-location', component_property='hash')])
        def update_location_on_page(hash_val):
            if hash_val is None:
                return ''

            return hash_val

        @app.callback(
            output=Output(component_id='test-search',
                          component_property='children'),
            inputs=[Input(component_id='test-location', component_property='search')])
        def update_location_on_page(search):
            if search is None:
                return ''

            return search

        @app.callback(
            output=Output(component_id='test-location',
                          component_property='pathname'),
            inputs=[Input(component_id='test-button',
                          component_property='n_clicks')],
            state=[State(component_id='test-location', component_property='pathname')])
        def update_pathname(n_clicks, current_pathname):
            if n_clicks is not None:
                return '/new/pathname'

            return current_pathname

        self.startServer(app=app)

        time.sleep(1)
        self.snapshot('link -- location')

        # Check that link updates pathname
        self.wait_for_element_by_css_selector('#test-link').click()
        self.assertEqual(
            self.driver.current_url.replace('http://localhost:8050', ''),
            '/test/pathname')
        self.wait_for_text_to_equal('#test-pathname', '/test/pathname')

        # Check that hash is updated in the Location
        self.wait_for_element_by_css_selector('#test-link-hash').click()
        self.wait_for_text_to_equal('#test-pathname', '/test/pathname')
        self.wait_for_text_to_equal('#test-hash', '#test')
        self.snapshot('link -- /test/pathname#test')

        # Check that search is updated in the Location -- note that this goes through href and therefore wipes the hash
        self.wait_for_element_by_css_selector('#test-link-search').click()
        self.wait_for_text_to_equal('#test-search', '?testQuery=testValue')
        self.wait_for_text_to_equal('#test-hash', '')
        self.snapshot('link -- /test/pathname?testQuery=testValue')

        # Check that pathname is updated through a Button click via props
        self.wait_for_element_by_css_selector('#test-button').click()
        self.wait_for_text_to_equal('#test-pathname', '/new/pathname')
        self.wait_for_text_to_equal('#test-search', '?testQuery=testValue')
        self.snapshot('link -- /new/pathname?testQuery=testValue')

        # Check that pathname is updated through an a tag click via props
        self.wait_for_element_by_css_selector('#test-a').click()
        try:
            self.wait_for_element_by_css_selector('#waitfor')
        except Exception as e:
            print(self.wait_for_element_by_css_selector(
                '#_dash-app-content').get_attribute('innerHTML'))
            raise e

        self.wait_for_text_to_equal('#test-pathname', '/test/pathname/a')
        self.wait_for_text_to_equal('#test-search', '')
        self.wait_for_text_to_equal('#test-hash', '')
        self.snapshot('link -- /test/pathname/a')

        # Check that hash is updated through an a tag click via props
        self.wait_for_element_by_css_selector('#test-a-hash').click()
        self.wait_for_text_to_equal('#test-pathname', '/test/pathname/a')
        self.wait_for_text_to_equal('#test-search', '')
        self.wait_for_text_to_equal('#test-hash', '#test-hash')
        self.snapshot('link -- /test/pathname/a#test-hash')

        # Check that hash is updated through an a tag click via props
        self.wait_for_element_by_css_selector('#test-a-query').click()
        self.wait_for_element_by_css_selector('#waitfor')
        self.wait_for_text_to_equal('#test-pathname', '/test/pathname/a')
        self.wait_for_text_to_equal('#test-search', '?queryA=valueA')
        self.wait_for_text_to_equal('#test-hash', '')
        self.snapshot('link -- /test/pathname/a?queryA=valueA')

# -*- coding: utf-8 -*-
import os
import sys
from multiprocessing import Lock
import time
import json
import re
import dash
from dash.dependencies import Input, Output, State
import dash_html_components as html
import dash_core_components as dcc
from dash.exceptions import PreventUpdate
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

try:
    from urlparse import urlparse
except ImportError:
    from urllib.parse import urlparse

from .IntegrationTests import IntegrationTests

TIMEOUT = 10


class Test1(IntegrationTests):
    def setUp(self):
        pass

    def wait_for_element_by_css_selector(self, selector):
        return WebDriverWait(self.driver, TIMEOUT).until(
            EC.presence_of_element_located((By.CSS_SELECTOR, selector))
        )

    def wait_for_text_to_equal(self, selector, assertion_text):
        def text_equal(driver):
            text = driver.find_element_by_css_selector(selector).text
            return text == assertion_text

        WebDriverWait(self.driver, TIMEOUT).until(text_equal)

    def snapshot(self, name):
        if "PERCY_PROJECT" in os.environ and "PERCY_TOKEN" in os.environ:
            python_version = sys.version.split(" ")[0]
            print("Percy Snapshot {}".format(python_version))
            self.percy_runner.snapshot(name=name)

    def test_loading_component_initialization(self):
        lock = Lock()

        app = dash.Dash(__name__)

        app.layout = html.Div([
            dcc.Loading([
                html.Div(id='div-1')
            ], className='loading')
        ], id='root')

        @app.callback(
            Output('div-1', 'children'),
            [Input('root', 'n_clicks')]
        )
        def updateDiv(children):
            with lock:
                return 'content'

        with lock:
            self.startServer(app)
            self.wait_for_element_by_css_selector(
                '.loading .dash-spinner'
            )

        self.wait_for_element_by_css_selector(
            '.loading #div-1'
        )

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_loading_component_action(self):
        lock = Lock()

        app = dash.Dash(__name__)

        app.layout = html.Div([
            dcc.Loading([
                html.Div(id='div-1')
            ], className='loading')
        ], id='root')

        @app.callback(
            Output('div-1', 'children'),
            [Input('root', 'n_clicks')]
        )
        def updateDiv(n_clicks):
            if n_clicks is not None:
                with lock:
                    return

            return 'content'

        with lock:
            self.startServer(app)
            self.wait_for_element_by_css_selector(
                '.loading #div-1'
            )

            self.driver.find_element_by_id('root').click()

            self.wait_for_element_by_css_selector(
                '.loading .dash-spinner'
            )

        self.wait_for_element_by_css_selector(
            '.loading #div-1'
        )

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_multiple_loading_components(self):
        lock = Lock()

        app = dash.Dash(__name__)

        app.layout = html.Div([
            dcc.Loading([
                html.Button(id='btn-1')
            ], className='loading-1'),
            dcc.Loading([
                html.Button(id='btn-2')
            ], className='loading-2')
        ], id='root')

        @app.callback(
            Output('btn-1', 'value'),
            [Input('btn-2', 'n_clicks')]
        )
        def updateDiv(n_clicks):
            if n_clicks is not None:
                with lock:
                    return

            return 'content'

        @app.callback(
            Output('btn-2', 'value'),
            [Input('btn-1', 'n_clicks')]
        )
        def updateDiv(n_clicks):
            if n_clicks is not None:
                with lock:
                    return

            return 'content'

        self.startServer(app)

        self.wait_for_element_by_css_selector(
            '.loading-1 #btn-1'
        )
        self.wait_for_element_by_css_selector(
            '.loading-2 #btn-2'
        )

        with lock:
            self.driver.find_element_by_id('btn-1').click()

            self.wait_for_element_by_css_selector(
                '.loading-2 .dash-spinner'
            )
            self.wait_for_element_by_css_selector(
                '.loading-1 #btn-1'
            )

        self.wait_for_element_by_css_selector(
            '.loading-2 #btn-2'
        )

        with lock:
            self.driver.find_element_by_id('btn-2').click()

            self.wait_for_element_by_css_selector(
                '.loading-1 .dash-spinner'
            )

        self.wait_for_element_by_css_selector(
            '.loading-1 #btn-1'
        )
        self.wait_for_element_by_css_selector(
            '.loading-2 #btn-2'
        )

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_nested_loading_components(self):
        lock = Lock()

        app = dash.Dash(__name__)

        app.layout = html.Div([
            dcc.Loading([
                html.Button(id='btn-1'),
                dcc.Loading([
                    html.Button(id='btn-2')
                ], className='loading-2')
            ], className='loading-1')
        ], id='root')

        @app.callback(
            Output('btn-1', 'value'),
            [Input('btn-2', 'n_clicks')]
        )
        def updateDiv(n_clicks):
            if n_clicks is not None:
                with lock:
                    return

            return 'content'

        @app.callback(
            Output('btn-2', 'value'),
            [Input('btn-1', 'n_clicks')]
        )
        def updateDiv(n_clicks):
            if n_clicks is not None:
                with lock:
                    return

            return 'content'

        self.startServer(app)

        self.wait_for_element_by_css_selector(
            '.loading-1 #btn-1'
        )
        self.wait_for_element_by_css_selector(
            '.loading-2 #btn-2'
        )

        with lock:
            self.driver.find_element_by_id('btn-1').click()

            self.wait_for_element_by_css_selector(
                '.loading-2 .dash-spinner'
            )
            self.wait_for_element_by_css_selector(
                '.loading-1 #btn-1'
            )

        self.wait_for_element_by_css_selector(
            '.loading-2 #btn-2'
        )

        with lock:
            self.driver.find_element_by_id('btn-2').click()

            self.wait_for_element_by_css_selector(
                '.loading-1 .dash-spinner'
            )

        self.wait_for_element_by_css_selector(
            '.loading-1 #btn-1'
        )
        self.wait_for_element_by_css_selector(
            '.loading-2 #btn-2'
        )

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_dynamic_loading_component(self):
        lock = Lock()

        app = dash.Dash(__name__)
        app.config['suppress_callback_exceptions'] = True

        app.layout = html.Div([
            html.Button(id='btn-1'),
            html.Div(id='div-1')
        ])

        @app.callback(
            Output('div-1', 'children'),
            [Input('btn-1', 'n_clicks')]
        )
        def updateDiv(n_clicks):
            if n_clicks is None:
                return

            with lock:
                return html.Div([
                    html.Button(id='btn-2'),
                    dcc.Loading([
                        html.Button(id='btn-3')
                    ], className='loading-1')
                ])

        @app.callback(
            Output('btn-3', 'content'),
            [Input('btn-2', 'n_clicks')]
        )
        def updateDynamic(n_clicks):
            if n_clicks is None:
                return

            with lock:
                return 'content'

        self.startServer(app)

        self.wait_for_element_by_css_selector(
            '#btn-1'
        )
        self.wait_for_element_by_css_selector(
            '#div-1'
        )

        self.driver.find_element_by_id('btn-1').click()

        self.wait_for_element_by_css_selector(
            '#div-1 #btn-2'
        )
        self.wait_for_element_by_css_selector(
            '.loading-1 #btn-3'
        )

        with lock:
            self.driver.find_element_by_id('btn-2').click()

            self.wait_for_element_by_css_selector(
                '.loading-1 .dash-spinner'
            )

        self.wait_for_element_by_css_selector(
            '#div-1 #btn-2'
        )
        self.wait_for_element_by_css_selector(
            '.loading-1 #btn-3'
        )

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_loading_slider(self):
        lock = Lock()

        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.Button(id='test-btn'),
            html.Label(id='test-div', children=['Horizontal Slider']),
            dcc.Slider(
                id='horizontal-slider',
                min=0,
                max=9,
                marks={i: 'Label {}'.format(i) if i == 1 else str(i)
                       for i in range(1, 6)},
                value=5,
            ),
        ])

        @app.callback(
            Output('horizontal-slider', 'value'),
            [Input('test-btn', 'n_clicks')]
        )
        def user_delayed_value(n_clicks):
            with lock:
                return 5

        with lock:
            self.startServer(app)

            self.wait_for_element_by_css_selector(
                '#horizontal-slider[data-dash-is-loading="true"]'
            )

        self.wait_for_element_by_css_selector(
            '#horizontal-slider:not([data-dash-is-loading="true"])'
        )

        with lock:
            self.driver.find_element_by_id('test-btn').click()

            self.wait_for_element_by_css_selector(
                '#horizontal-slider[data-dash-is-loading="true"]'
            )

        self.wait_for_element_by_css_selector(
            '#horizontal-slider:not([data-dash-is-loading="true"])'
        )

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_horizontal_slider(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.Label('Horizontal Slider'),
            dcc.Slider(
                id='horizontal-slider',
                min=0,
                max=9,
                marks={i: 'Label {}'.format(i) if i == 1 else str(i)
                       for i in range(1, 6)},
                value=5,
            ),
        ])
        self.startServer(app)

        self.wait_for_element_by_css_selector('#horizontal-slider')
        self.snapshot('horizontal slider')

        h_slider = self.driver.find_element_by_css_selector(
            '#horizontal-slider div[role="slider"]'
        )
        h_slider.click()

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_vertical_slider(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.Label('Vertical Slider'),
            dcc.Slider(
                id='vertical-slider',
                min=0,
                max=9,
                marks={i: 'Label {}'.format(i) if i == 1 else str(i)
                       for i in range(1, 6)},
                value=5,
                vertical=True,
            ),
        ], style={'height': '500px'})
        self.startServer(app)

        self.wait_for_element_by_css_selector('#vertical-slider')
        self.snapshot('vertical slider')

        v_slider = self.driver.find_element_by_css_selector(
            '#vertical-slider div[role="slider"]'
        )
        v_slider.click()

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_loading_range_slider(self):
        lock = Lock()

        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.Button(id='test-btn'),
            html.Label(id='test-div', children=['Horizontal Range Slider']),
            dcc.RangeSlider(
                id='horizontal-range-slider',
                min=0,
                max=9,
                marks={i: 'Label {}'.format(i) if i == 1 else str(i)
                       for i in range(1, 6)},
                value=[4, 6],
            ),
        ])

        @app.callback(
            Output('horizontal-range-slider', 'value'),
            [Input('test-btn', 'n_clicks')]
        )
        def delayed_value(children):
            with lock:
                return [4, 6]

        with lock:
            self.startServer(app)

            self.wait_for_element_by_css_selector(
                '#horizontal-range-slider[data-dash-is-loading="true"]'
            )

        self.wait_for_element_by_css_selector(
            '#horizontal-range-slider:not([data-dash-is-loading="true"])'
        )

        with lock:
            self.driver.find_element_by_id('test-btn').click()

            self.wait_for_element_by_css_selector(
                '#horizontal-range-slider[data-dash-is-loading="true"]'
            )

        self.wait_for_element_by_css_selector(
            '#horizontal-range-slider:not([data-dash-is-loading="true"])'
        )

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_horizontal_range_slider(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.Label('Horizontal Range Slider'),
            dcc.RangeSlider(
                id='horizontal-range-slider',
                min=0,
                max=9,
                marks={i: 'Label {}'.format(i) if i == 1 else str(i)
                       for i in range(1, 6)},
                value=[4, 6],
            ),
        ])
        self.startServer(app)

        self.wait_for_element_by_css_selector('#horizontal-range-slider')
        self.snapshot('horizontal range slider')

        h_slider_1 = self.driver.find_element_by_css_selector(
            '#horizontal-range-slider div.rc-slider-handle-1[role="slider"]'
        )
        h_slider_1.click()

        h_slider_2 = self.driver.find_element_by_css_selector(
            '#horizontal-range-slider div.rc-slider-handle-2[role="slider"]'
        )
        h_slider_2.click()

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_vertical_range_slider(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.Label('Vertical Range Slider'),
            dcc.RangeSlider(
                id='vertical-range-slider',
                min=0,
                max=9,
                marks={i: 'Label {}'.format(i) if i == 1 else str(i)
                       for i in range(1, 6)},
                value=[4, 6],
                vertical=True,
            ),
        ], style={'height': '500px'})
        self.startServer(app)

        self.wait_for_element_by_css_selector('#vertical-range-slider')
        self.snapshot('vertical range slider')

        v_slider_1 = self.driver.find_element_by_css_selector(
            '#vertical-range-slider div.rc-slider-handle-1[role="slider"]'
        )
        v_slider_1.click()

        v_slider_2 = self.driver.find_element_by_css_selector(
            '#vertical-range-slider div.rc-slider-handle-2[role="slider"]'
        )
        v_slider_2.click()

        for entry in self.get_log():
            raise Exception('browser error logged during test', entry)

    def test_tabs_in_vertical_mode(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            dcc.Tabs(id="tabs", value='tab-3', children=[
                dcc.Tab(label='Tab one', value='tab-1', id='tab-1', children=[
                    html.Div('Tab One Content')
                ]),
                dcc.Tab(label='Tab two', value='tab-2', id='tab-2', children=[
                    html.Div('Tab Two Content')
                ]),
                dcc.Tab(label='Tab three', value='tab-3', id='tab-3', children=[
                    html.Div('Tab Three Content')
                ]),
            ], vertical=True),
            html.Div(id='tabs-content')
        ])

        self.startServer(app=app)
        self.wait_for_text_to_equal('#tab-3', 'Tab three')

        self.snapshot('Tabs - vertical mode')

    def test_tabs_without_children(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.H1('Dash Tabs component demo'),
            dcc.Tabs(id="tabs", value='tab-2', children=[
                dcc.Tab(label='Tab one', value='tab-1', id='tab-1'),
                dcc.Tab(label='Tab two', value='tab-2', id='tab-2'),
            ]),
            html.Div(id='tabs-content')
        ])

        @app.callback(dash.dependencies.Output('tabs-content', 'children'),
                      [dash.dependencies.Input('tabs', 'value')])
        def render_content(tab):
            if tab == 'tab-1':
                return html.Div([
                    html.H3('Test content 1')
                ], id='test-tab-1')
            elif tab == 'tab-2':
                return html.Div([
                    html.H3('Test content 2')
                ], id='test-tab-2')

        self.startServer(app=app)

        self.wait_for_text_to_equal('#tabs-content', 'Test content 2')
        self.snapshot('initial tab - tab 2')

        selected_tab = self.wait_for_element_by_css_selector('#tab-1')
        selected_tab.click()
        time.sleep(1)
        self.wait_for_text_to_equal('#tabs-content', 'Test content 1')

    def test_tabs_with_children_undefined(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.H1('Dash Tabs component demo'),
            dcc.Tabs(id="tabs", value='tab-1'),
            html.Div(id='tabs-content')
        ])

        self.startServer(app=app)

        self.wait_for_element_by_css_selector('#tabs-content')

        self.snapshot('Tabs component with children undefined')

    def test_tabs_render_without_selected(self):
        app = dash.Dash(__name__)

        data = [
            {'id': 'one', 'value': 1},
            {'id': 'two', 'value': 2},
        ]

        menu = html.Div([
            html.Div('one', id='one'),
            html.Div('two', id='two')
        ])

        tabs_one = html.Div([
            dcc.Tabs([
                dcc.Tab(dcc.Graph(id='graph-one'), label='tab-one-one'),
            ])
        ], id='tabs-one', style={'display': 'none'})

        tabs_two = html.Div([
            dcc.Tabs([
                dcc.Tab(dcc.Graph(id='graph-two'), label='tab-two-one'),
            ])
        ], id='tabs-two', style={'display': 'none'})

        app.layout = html.Div([
            menu,
            tabs_one,
            tabs_two
        ])

        for i in ('one', 'two'):

            @app.callback(Output('tabs-{}'.format(i), 'style'),
                          [Input(i, 'n_clicks')])
            def on_click(n_clicks):
                if n_clicks is None:
                    raise PreventUpdate

                if n_clicks % 2 == 1:
                    return {'display': 'block'}
                return {'display': 'none'}

            @app.callback(Output('graph-{}'.format(i), 'figure'),
                          [Input(i, 'n_clicks')])
            def on_click(n_clicks):
                if n_clicks is None:
                    raise PreventUpdate

                return {
                    'data': [
                        {
                            'x': [1, 2, 3, 4],
                            'y': [4, 3, 2, 1]
                        }
                    ],
                    'layout': {
                        'width': 700,
                        'height': 450
                    }
                }

        self.startServer(app=app)

        button_one = self.wait_for_element_by_css_selector('#one')
        button_two = self.wait_for_element_by_css_selector('#two')

        button_one.click()

        # wait for tabs to be loaded after clicking
        WebDriverWait(self.driver, 10).until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, "#graph-one .main-svg"))
        )

        time.sleep(1)
        self.snapshot("Tabs 1 rendered ")

        button_two.click()

        # wait for tabs to be loaded after clicking
        WebDriverWait(self.driver, 10).until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, "#graph-two .main-svg"))
        )

        time.sleep(1)
        self.snapshot("Tabs 2 rendered ")

        # do some extra tests while we're here
        # and have access to Graph and plotly.js
        self.check_graph_config_shape()
        self.check_plotlyjs()

    def check_plotlyjs(self):
        # find plotly.js files in the dist folder, check that there's only one
        all_dist = os.listdir(dcc.__path__[0])
        js_re = r'^plotly-(.*)\.min\.js$'
        plotlyjs_dist = [fn for fn in all_dist if re.match(js_re, fn)]

        self.assertEqual(len(plotlyjs_dist), 1)

        # check that the version matches what we see in the page
        page_version = self.driver.execute_script('return Plotly.version;')
        dist_version = re.match(js_re, plotlyjs_dist[0]).groups()[0]
        self.assertEqual(page_version, dist_version)

    def check_graph_config_shape(self):
        config_schema = self.driver.execute_script(
            'return Plotly.PlotSchema.get().config;'
        )
        with open(os.path.join(dcc.__path__[0], 'metadata.json')) as meta:
            graph_meta = json.load(meta)['src/components/Graph.react.js']
            config_prop_shape = graph_meta['props']['config']['type']['value']

        ignored_config = [
            'setBackground',
            'showSources',
            'logging',
            'globalTransforms',
            'role'
        ]

        def crawl(schema, props):
            for prop_name in props:
                self.assertIn(prop_name, schema)

            for item_name, item in schema.items():
                if item_name in ignored_config:
                    continue

                self.assertIn(item_name, props)
                if 'valType' not in item:
                    crawl(item, props[item_name]['value'])

        crawl(config_schema, config_prop_shape)

    def test_tabs_without_value(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.H1('Dash Tabs component demo'),
            dcc.Tabs(id="tabs-without-value", children=[
                dcc.Tab(label='Tab One', value='tab-1'),
                dcc.Tab(label='Tab Two', value='tab-2'),
            ]),
            html.Div(id='tabs-content')
        ])

        @app.callback(Output('tabs-content', 'children'),
                      [Input('tabs-without-value', 'value')])
        def render_content(tab):
            if tab == 'tab-1':
                return html.H3('Default selected Tab content 1')
            elif tab == 'tab-2':
                return html.H3('Tab content 2')

        self.startServer(app=app)

        self.wait_for_text_to_equal('#tabs-content', 'Default selected Tab content 1')

        self.snapshot('Tab 1 should be selected by default')

    def test_graph_does_not_resize_in_tabs(self):
        app = dash.Dash(__name__)
        app.layout = html.Div([
            html.H1('Dash Tabs component demo'),
            dcc.Tabs(id="tabs-example", value='tab-1-example', children=[
                dcc.Tab(label='Tab One', value='tab-1-example', id='tab-1'),
                dcc.Tab(label='Tab Two', value='tab-2-example', id='tab-2'),
            ]),
            html.Div(id='tabs-content-example')
        ])

        @app.callback(Output('tabs-content-example', 'children'),
                      [Input('tabs-example', 'value')])
        def render_content(tab):
            if tab == 'tab-1-example':
                return html.Div([
                    html.H3('Tab content 1'),
                    dcc.Graph(
                        id='graph-1-tabs',
                        figure={
                            'data': [{
                                'x': [1, 2, 3],
                                'y': [3, 1, 2],
                                'type': 'bar'
                            }]
                        }
                    )
                ])
            elif tab == 'tab-2-example':
                return html.Div([
                    html.H3('Tab content 2'),
                    dcc.Graph(
                        id='graph-2-tabs',
                        figure={
                            'data': [{
                                'x': [1, 2, 3],
                                'y': [5, 10, 6],
                                'type': 'bar'
                            }]
                        }
                    )
                ])
        self.startServer(app=app)

        tab_one = self.wait_for_element_by_css_selector('#tab-1')
        tab_two = self.wait_for_element_by_css_selector('#tab-2')

        WebDriverWait(self.driver, 10).until(
            EC.element_to_be_clickable((By.ID, "tab-2"))
        )

        self.snapshot("Tabs with Graph - initial (graph should not resize)")
        tab_two.click()

        # wait for Graph's internal svg to be loaded after clicking
        WebDriverWait(self.driver, 10).until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, "#graph-2-tabs .main-svg"))
        )

        self.snapshot("Tabs with Graph - clicked tab 2 (graph should not resize)")

        WebDriverWait(self.driver, 10).until(
            EC.element_to_be_clickable((By.ID, "tab-1"))
        )

        tab_one.click()

        # wait for Graph to be loaded after clicking
        WebDriverWait(self.driver, 10).until(
            EC.visibility_of_element_located((By.CSS_SELECTOR, "#graph-1-tabs .main-svg"))
        )

        self.snapshot("Tabs with Graph - clicked tab 1 (graph should not resize)")

    def test_location_link(self):
        app = dash.Dash(__name__)

        app.layout = html.Div([
            html.Div(id='waitfor'),
            dcc.Location(id='test-location', refresh=False),

            dcc.Link(
                html.Button('I am a clickable button'),
                id='test-link',
                href='/test/pathname'),
            dcc.Link(
                html.Button('I am a clickable hash button'),
                id='test-link-hash',
                href='#test'),
            dcc.Link(
                html.Button('I am a clickable search button'),
                id='test-link-search',
                href='?testQuery=testValue',
                refresh=False),
            html.Button('I am a magic button that updates pathname',
                        id='test-button'),
            html.A('link to click', href='/test/pathname/a', id='test-a'),
            html.A('link to click', href='#test-hash', id='test-a-hash'),
            html.A('link to click', href='?queryA=valueA', id='test-a-query'),
            html.Div(id='test-pathname', children=[]),
            html.Div(id='test-hash', children=[]),
            html.Div(id='test-search', children=[]),
        ])

        @app.callback(
            output=Output(component_id='test-pathname',
                          component_property='children'),
            inputs=[Input(component_id='test-location', component_property='pathname')])
        def update_location_on_page(pathname):
            return pathname

        @app.callback(
            output=Output(component_id='test-hash',
                          component_property='children'),
            inputs=[Input(component_id='test-location', component_property='hash')])
        def update_location_on_page(hash_val):
            if hash_val is None:
                return ''

            return hash_val

        @app.callback(
            output=Output(component_id='test-search',
                          component_property='children'),
            inputs=[Input(component_id='test-location', component_property='search')])
        def update_location_on_page(search):
            if search is None:
                return ''

            return search

        @app.callback(
            output=Output(component_id='test-location',
                          component_property='pathname'),
            inputs=[Input(component_id='test-button',
                          component_property='n_clicks')],
            state=[State(component_id='test-location', component_property='pathname')])
        def update_pathname(n_clicks, current_pathname):
            if n_clicks is not None:
                return '/new/pathname'

            return current_pathname

        self.startServer(app=app)

        time.sleep(1)
        self.snapshot('link -- location')

        # Check that link updates pathname
        self.wait_for_element_by_css_selector('#test-link').click()
        self.assertEqual(
            self.driver.current_url.replace('http://localhost:8050', ''),
            '/test/pathname')
        self.wait_for_text_to_equal('#test-pathname', '/test/pathname')

        # Check that hash is updated in the Location
        self.wait_for_element_by_css_selector('#test-link-hash').click()
        self.wait_for_text_to_equal('#test-pathname', '/test/pathname')
        self.wait_for_text_to_equal('#test-hash', '#test')
        self.snapshot('link -- /test/pathname#test')

        # Check that search is updated in the Location -- note that this goes through href and therefore wipes the hash
        self.wait_for_element_by_css_selector('#test-link-search').click()
        self.wait_for_text_to_equal('#test-search', '?testQuery=testValue')
        self.wait_for_text_to_equal('#test-hash', '')
        self.snapshot('link -- /test/pathname?testQuery=testValue')

        # Check that pathname is updated through a Button click via props
        self.wait_for_element_by_css_selector('#test-button').click()
        self.wait_for_text_to_equal('#test-pathname', '/new/pathname')
        self.wait_for_text_to_equal('#test-search', '?testQuery=testValue')
        self.snapshot('link -- /new/pathname?testQuery=testValue')

        # Check that pathname is updated through an a tag click via props
        self.wait_for_element_by_css_selector('#test-a').click()
        try:
            self.wait_for_element_by_css_selector('#waitfor')
        except Exception as e:
            print(self.wait_for_element_by_css_selector(
                '#_dash-app-content').get_attribute('innerHTML'))
            raise e

        self.wait_for_text_to_equal('#test-pathname', '/test/pathname/a')
        self.wait_for_text_to_equal('#test-search', '')
        self.wait_for_text_to_equal('#test-hash', '')
        self.snapshot('link -- /test/pathname/a')

        # Check that hash is updated through an a tag click via props
        self.wait_for_element_by_css_selector('#test-a-hash').click()
        self.wait_for_text_to_equal('#test-pathname', '/test/pathname/a')
        self.wait_for_text_to_equal('#test-search', '')
        self.wait_for_text_to_equal('#test-hash', '#test-hash')
        self.snapshot('link -- /test/pathname/a#test-hash')

        # Check that hash is updated through an a tag click via props
        self.wait_for_element_by_css_selector('#test-a-query').click()
        self.wait_for_element_by_css_selector('#waitfor')
        self.wait_for_text_to_equal('#test-pathname', '/test/pathname/a')
        self.wait_for_text_to_equal('#test-search', '?queryA=valueA')
        self.wait_for_text_to_equal('#test-hash', '')
        self.snapshot('link -- /test/pathname/a?queryA=valueA')
# -*- coding: utf-8 -*- #div-1' #div-1' #div-1' #btn-1' #btn-2' #btn-1' #btn-2' #btn-1' #btn-2' #btn-1' #btn-2' #btn-1' #btn-2' #btn-1' #btn-2' #btn-2' #btn-3' #btn-2' #btn-3' # wait for tabs to be loaded after clicking # wait for tabs to be loaded after clicking # do some extra tests while we're here # and have access to Graph and plotly.js # find plotly.js files in the dist folder, check that there's only one # check that the version matches what we see in the page # wait for Graph's internal svg to be loaded after clicking # wait for Graph to be loaded after clicking # Check that link updates pathname # Check that hash is updated in the Location #test') # Check that search is updated in the Location -- note that this goes through href and therefore wipes the hash # Check that pathname is updated through a Button click via props # Check that pathname is updated through an a tag click via props # Check that hash is updated through an a tag click via props #test-hash') # Check that hash is updated through an a tag click via props