Repository: vega/altair

Issue:
alt.Tooltip not behaving the same way as not specifying the object
Excuse the weird title but I am not sure how to properly phrase the issue. This is on Altair 3.1.

Let's take the following example code from another issue: 

```
from vega_datasets import data
df=data.barley()
base = alt.Chart(df).transform_joinaggregate(
    yield_sum='sum(yield)',
    groupby=['year', 'variety']
).transform_calculate(
    yield_norm='datum.yield / datum.yield_sum'  
).mark_bar().encode(
    x=alt.X('yield', stack='normalize', axis=alt.Axis(title='Normalized Yield', format='.0%')),
    y=alt.Y('variety', title='Variety'),
    tooltip=['yield', 'variety', 'year', 'yield_norm:Q'],
    order='site'
)

bars = base.encode(
    detail='site',
    color=alt.Color('site', title='Site')
)

text = base.mark_text(dx=-15, dy=3).encode(
    text=alt.Text('yield_norm:Q', format='.0%'),
)

alt.layer(bars, text).facet(column=alt.Column('year:O', title='Year'))
```

This works fine. However changing the tooltip line to
`tooltip=alt.Tooltip(['yield', 'variety', 'year', 'yield_norm:Q'])`

will throw the following error:

> ValueError: yield encoding field is specified without a type; the type cannot be automatically inferred because the data is not specified as a pandas.DataFrame.

Should the two ways of specifying the tooltip not work interchangeably? Or is it maybe related to this issue #1573?


Discussion:
Yes, the type should propagate similarly in each case. I suspect internally the ``context`` argument is not being properly propagated in this case.
#1587 should fix this

Resolving patch:
diff --git a/altair/vegalite/v2/schema/channels.py b/altair/vegalite/v2/schema/channels.py
--- a/altair/vegalite/v2/schema/channels.py
+++ b/altair/vegalite/v2/schema/channels.py
@@ -24,7 +24,7 @@ def to_dict(self, validate=True, ignore=(), context=None):
             # If given a list of shorthands, then transform it to a list of classes
             kwds = self._kwds.copy()
             kwds.pop('shorthand')
-            return [self.__class__(shorthand, **kwds).to_dict()
+            return [self.__class__(shorthand, **kwds).to_dict(validate=validate, ignore=ignore, context=context)
                     for shorthand in self.shorthand]
         elif isinstance(self.shorthand, six.string_types):
             kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
diff --git a/altair/vegalite/v3/schema/channels.py b/altair/vegalite/v3/schema/channels.py
--- a/altair/vegalite/v3/schema/channels.py
+++ b/altair/vegalite/v3/schema/channels.py
@@ -24,7 +24,7 @@ def to_dict(self, validate=True, ignore=(), context=None):
             # If given a list of shorthands, then transform it to a list of classes
             kwds = self._kwds.copy()
             kwds.pop('shorthand')
-            return [self.__class__(shorthand, **kwds).to_dict()
+            return [self.__class__(shorthand, **kwds).to_dict(validate=validate, ignore=ignore, context=context)
                     for shorthand in self.shorthand]
         elif isinstance(self.shorthand, six.string_types):
             kwds = parse_shorthand(self.shorthand, data=context.get('data', None))
diff --git a/tools/generate_schema_wrapper.py b/tools/generate_schema_wrapper.py
--- a/tools/generate_schema_wrapper.py
+++ b/tools/generate_schema_wrapper.py
@@ -88,7 +88,7 @@ def to_dict(self, validate=True, ignore=(), context=None):
             # If given a list of shorthands, then transform it to a list of classes
             kwds = self._kwds.copy()
             kwds.pop('shorthand')
-            return [self.__class__(shorthand, **kwds).to_dict()
+            return [self.__class__(shorthand, **kwds).to_dict(validate=validate, ignore=ignore, context=context)
                     for shorthand in self.shorthand]
         elif isinstance(self.shorthand, six.string_types):
             kwds = parse_shorthand(self.shorthand, data=context.get('data', None))


Test patch:
diff --git a/altair/vegalite/v3/tests/test_api.py b/altair/vegalite/v3/tests/test_api.py
--- a/altair/vegalite/v3/tests/test_api.py
+++ b/altair/vegalite/v3/tests/test_api.py
@@ -19,6 +19,10 @@
     selenium = None
 
 
+def getargs(*args, **kwargs):
+    return args, kwargs
+
+
 OP_DICT = {
     'layer': operator.add,
     'hconcat': operator.or_,
@@ -111,28 +115,27 @@ def _check_encodings(chart):
     assert dct['encoding']['y']['type'] == 'ordinal'
 
 
-def test_multiple_encodings():
+@pytest.mark.parametrize('args, kwargs', [
+    getargs(detail=['value:Q', 'name:N'], tooltip=['value:Q', 'name:N']),
+    getargs(detail=['value', 'name'], tooltip=['value', 'name']),
+    getargs(alt.Detail(['value:Q', 'name:N']), alt.Tooltip(['value:Q', 'name:N'])),
+    getargs(alt.Detail(['value', 'name']), alt.Tooltip(['value', 'name'])),
+    getargs([alt.Detail('value:Q'), alt.Detail('name:N')],
+            [alt.Tooltip('value:Q'), alt.Tooltip('name:N')]),
+    getargs([alt.Detail('value'), alt.Detail('name')],
+            [alt.Tooltip('value'), alt.Tooltip('name')]),
+])
+def test_multiple_encodings(args, kwargs):
+    df = pd.DataFrame({
+        'value': [1, 2, 3],
+        'name': ['A', 'B', 'C'],
+    })
     encoding_dct = [{'field': 'value', 'type': 'quantitative'},
                     {'field': 'name', 'type': 'nominal'}]
-    chart1 = alt.Chart('data.csv').mark_point().encode(
-        detail=['value:Q', 'name:N'],
-        tooltip=['value:Q', 'name:N']
-    )
-
-    chart2 = alt.Chart('data.csv').mark_point().encode(
-        alt.Detail(['value:Q', 'name:N']),
-        alt.Tooltip(['value:Q', 'name:N'])
-    )
-
-    chart3 = alt.Chart('data.csv').mark_point().encode(
-        [alt.Detail('value:Q'), alt.Detail('name:N')],
-        [alt.Tooltip('value:Q'), alt.Tooltip('name:N')]
-    )
-
-    for chart in [chart1, chart2, chart3]:
-        dct = chart.to_dict()
-        assert dct['encoding']['detail'] == encoding_dct
-        assert dct['encoding']['tooltip'] == encoding_dct
+    chart = alt.Chart(df).mark_point().encode(*args, **kwargs)
+    dct = chart.to_dict()
+    assert dct['encoding']['detail'] == encoding_dct
+    assert dct['encoding']['tooltip'] == encoding_dct
 
 
 def test_chart_operations():