1# Copyright (c) 2004 Divmod.
2# See LICENSE for details.
3
4from zope.interface import implements
5
6from twisted.python import components
7
8from nevow import tags
9from nevow import inevow
10from nevow import context
11from nevow import util
12
13import formless
14from formless import webform
15from formless import iformless
16from formless import configurable
17
18from nevow.test import test_flatstan
19
20class Base(test_flatstan.Base):
21    implements(iformless.IConfigurableFactory)
22
23    synchronousLocateConfigurable = False
24
25    def locateConfigurable(self, *args, **kw):
26        r = iformless.IConfigurable(self.conf)
27        if not self.synchronousLocateConfigurable:
28            r = util.succeed(r)
29        return r
30
31    def setupContext(self, *args, **kw):
32        ctx = test_flatstan.Base.setupContext(self, *args, **kw)
33        return context.PageContext(tag=tags.html(), parent=ctx)
34
35    def render(self, tag, setupContext=lambda c:c):
36        return test_flatstan.Base.render(
37            self, tag, setupContext=setupContext,
38            wantDeferred=True)
39
40    def renderForms(self, configurable, ctx=None, *args, **kwargs):
41        self.conf = configurable
42        if ctx is None:
43            ctx = self.setupContext(False)
44        ctx.remember(self, iformless.IConfigurableFactory)
45        return self.render(
46            webform.renderForms(*args, **kwargs),
47            setupContext=lambda c: ctx)
48
49    def postForm(self, ctx, obj, bindingName, args):
50        self.conf = obj
51        ctx.remember(self, iformless.IConfigurableFactory)
52
53        def trapValidate(f):
54            f.trap(formless.ValidateError)
55            e = f.value
56            errors = ctx.locate(iformless.IFormErrors)
57            ## Set the overall error for this form
58            errors.setError(bindingName, e.formErrorMessage)
59            errors.updateErrors(bindingName, e.errors)
60            ctx.locate(iformless.IFormDefaults).getAllDefaults(bindingName).update(e.partialForm)
61
62        return util.maybeDeferred(self.locateConfigurable,obj).addCallback(lambda x: x.postForm(
63            ctx, bindingName, args
64            )).addErrback(trapValidate)
65
66
67class Complete(Base):
68    def test_configureProperty(self):
69        class IStupid(formless.TypedInterface):
70            foo = formless.String()
71
72        class StupidThing(configurable.Configurable):
73            implements(IStupid)
74
75            def __init__(self):
76                configurable.Configurable.__init__(self, None)
77                self.foo = 'foo'
78
79        dumb = StupidThing()
80
81        def doasserts(val):
82            self.assertSubstring('freeform_post!!foo', val)
83            self.assertSubstring('foo', val)
84            self.assertSubstring('type="text"', val)
85            self.assertSubstring('<input name="change" type="submit"', val)
86        return self.renderForms(dumb).addCallback(doasserts)
87
88
89    def test_configureMethod(self):
90        class IDumb(formless.TypedInterface):
91            def foo(bar=formless.String()):
92                return formless.String()
93            foo = formless.autocallable(foo)
94
95        class DumbThing(configurable.Configurable):
96            implements(IDumb)
97
98            def foo(self, bar):
99                return "baz"
100
101        stupid = DumbThing(1)
102
103        def doasserts(val):
104            self.assertSubstring('freeform_post!!foo', val)
105            self.assertSubstring('foo', val)
106            self.assertSubstring('bar', val)
107        return self.renderForms(stupid).addCallback(doasserts)
108
109
110class BuildingBlocksTest(Base):
111    def test_1_renderTyped(self):
112        binding = formless.Property('hello', formless.String(
113            label="Hello",
114            description="Hello, world."))
115
116        ## Look up a renderer specific to the type of our binding, typedValue;
117        renderer = iformless.ITypedRenderer(
118            binding.typedValue, None)
119
120        ## But render the binding itself with this renderer
121        ## The binding has the ".name" attribute we need
122        def later(val):
123            self.assertSubstring('hello', val)
124            self.assertSubstring('Hello', val)
125            self.assertSubstring('Hello, world.', val)
126            self.failIfSubstring('</form>', val)
127            self.failIfSubstring('<input type="submit"', val)
128        return self.render(tags.invisible(data=binding, render=renderer)).addCallback(later)
129
130    test_1_renderTyped.todo = "Render binding"
131
132    def test_2_renderPropertyBinding(self):
133        binding = formless.Property('goodbye', formless.String(
134            label="Goodbye",
135            description="Goodbye cruel world"))
136
137        # Look up an IBindingRenderer, which will render the form and the typed
138        renderer = iformless.IBindingRenderer(binding)
139        def later(val):
140            self.assertSubstring('<form ', val)
141            self.assertSubstring('<input name="change" type="submit"', val)
142            self.assertSubstring('name="goodbye"', val)
143            self.assertSubstring('Goodbye', val)
144            self.assertSubstring('Goodbye cruel world', val)
145        return self.render(tags.invisible(data=binding, render=renderer)).addCallback(later)
146
147    def test_3_renderMethodBinding(self):
148        binding = formless.MethodBinding('doit', formless.Method(
149            returnValue=None,
150            arguments=[formless.Argument('foo', formless.String(label="Foo"))],
151            label="Do It",
152            description="Do it to 'em all"))
153
154        renderer = iformless.IBindingRenderer(binding)
155
156        def later(val):
157            self.assertSubstring('<form ', val)
158            self.assertSubstring('Do It', val)
159            self.assertSubstring("Do it to 'em all", val)
160            self.assertSubstring("Foo", val)
161            self.assertSubstring('name="foo"', val)
162        return self.render(tags.invisible(data=binding, render=renderer)).addCallback(later)
163
164
165class TestDefaults(Base):
166    def test_1_renderWithDefaultValues(self):
167        binding = formless.MethodBinding('haveFun', formless.Method(
168            returnValue=None,
169            arguments=[formless.Argument('funValue', formless.Integer(label="Fun Value", default=0))]
170        ))
171
172        def setupCtx(ctx):
173            ctx.locate(iformless.IFormDefaults).setDefault('funValue', 15)
174            return ctx
175
176        renderer = iformless.IBindingRenderer(binding)
177        def later(val):
178            self.failIfSubstring('0', val)
179            self.assertSubstring('15', val)
180        return self.render(tags.invisible(data=binding, render=renderer), setupContext=setupCtx).addCallback(
181            later)
182
183    def test_2_renderWithObjectPropertyValues(self):
184        class IDefaultProperty(formless.TypedInterface):
185            default = formless.Integer(default=2)
186
187        class Foo(configurable.Configurable):
188            implements(IDefaultProperty)
189            default = 54
190
191        def later(val):
192            self.failIfSubstring('2', val)
193            self.assertSubstring('54', val)
194        return self.renderForms(Foo(None)).addCallback(later)
195
196    def test_3_renderWithAdapteeAttributeValues(self):
197        class IDefaultProperty(formless.TypedInterface):
198            default = formless.Integer(default=2)
199
200        class Adaptee(object):
201            default = 69
202
203        class Bar(configurable.Configurable):
204            implements(IDefaultProperty)
205
206        def later(val):
207            self.failIfSubstring('2', val)
208            self.assertSubstring('69', val)
209        return self.renderForms(Bar(Adaptee())).addCallback(later)
210
211    def test_4_testBindingDefaults(self):
212        class IBindingDefaults(formless.TypedInterface):
213            def aMethod(foo=formless.String(default="The foo")):
214                pass
215            aMethod = formless.autocallable(aMethod)
216
217            aProperty = formless.String(default="The property")
218
219        class Implements(configurable.Configurable):
220            implements(IBindingDefaults)
221
222        def later(val):
223            self.assertSubstring("The foo", val)
224            self.assertSubstring("The property", val)
225        return self.renderForms(Implements(None)).addCallback(later)
226
227    def test_5_testDynamicDefaults(self):
228        class IDynamicDefaults(formless.TypedInterface):
229            def aMethod(foo=formless.String(default="NOTFOO")):
230                pass
231            def bMethod(foo=formless.String(default="NOTBAR")):
232                pass
233            aMethod = formless.autocallable(aMethod)
234            bMethod = formless.autocallable(bMethod)
235
236        class Implements(configurable.Configurable):
237            implements(IDynamicDefaults)
238
239        def later(val):
240            self.assertSubstring("YESFOO", val)
241            self.assertSubstring("YESBAR", val)
242            self.assertNotSubstring("NOTFOO", val)
243            self.assertNotSubstring("NOTBAR", val)
244
245        return self.renderForms(Implements(None), bindingDefaults={
246                'aMethod': {'foo': 'YESFOO'},
247                'bMethod': {'foo': 'YESBAR'}}).addCallback(later)
248
249
250class TestNonConfigurableSubclass(Base):
251    def test_1_testSimple(self):
252        class ISimpleTypedInterface(formless.TypedInterface):
253            anInt = formless.Integer()
254            def aMethod(aString = formless.String()):
255                return None
256            aMethod = formless.autocallable(aMethod)
257
258        class ANonConfigurable(object): # Not subclassing Configurable
259            implements(ISimpleTypedInterface) # But implements a TypedInterface
260
261        def later(val):
262            self.assertSubstring('anInt', val)
263            self.assertSubstring('aMethod', val)
264
265        return self.renderForms(ANonConfigurable()).addCallback(later)
266
267
268
269class TestPostAForm(Base):
270    def test_1_failAndSucceed(self):
271        class IAPasswordMethod(formless.TypedInterface):
272            def password(pword = formless.Password(), integer=formless.Integer()):
273                pass
274            password = formless.autocallable(password)
275
276        class APasswordImplementation(object):
277            implements(IAPasswordMethod)
278            matched = False
279            def password(self, pword, integer):
280                self.matched = True
281                return "password matched"
282
283        theObj = APasswordImplementation()
284        ctx = self.setupContext()
285
286        D = self.postForm(ctx, theObj, "password", {"pword": ["these passwords"], "pword____2": ["don't match"], 'integer': ['Not integer']})
287        def after(result):
288            self.assertEquals(theObj.matched, False)
289            def later(val):
290                self.assertSubstring("Passwords do not match. Please reenter.", val)
291                self.assertSubstring('value="Not integer"', val)
292            return self.renderForms(theObj, ctx).addCallback(later)
293        return D.addCallback(after)
294
295    def test_2_propertyFailed(self):
296        class IAProperty(formless.TypedInterface):
297            prop = formless.Integer()
298
299        class Impl(object):
300            implements(IAProperty)
301            prop = 5
302
303        theObj = Impl()
304        ctx = self.setupContext()
305        D = self.postForm(ctx, theObj, 'prop', {'prop': ['bad']})
306        def after(result):
307            def later(val):
308                self.assertSubstring('value="bad"', val)
309            return self.renderForms(theObj, ctx).addCallback(later)
310        return D.addCallback(after)
311
312
313class TestRenderPropertyGroup(Base):
314    def test_1_propertyGroup(self):
315        class Outer(formless.TypedInterface):
316            class Inner(formless.TypedInterface):
317                one = formless.Integer()
318                two = formless.Integer()
319
320                def buckleMyShoe():
321                    pass
322                buckleMyShoe = formless.autocallable(buckleMyShoe)
323
324                def buriedAlive():
325                    pass
326                buriedAlive = formless.autocallable(buriedAlive)
327
328        class Implementation(object):
329            implements(Outer)
330            one = 1
331            two = 2
332            buckled = False
333            buried = False
334            def buckleMyShoe(self):
335                self.buckled = True
336            def buriedAlive(self):
337                self.buried = True
338
339        impl = Implementation()
340        ctx = self.setupContext()
341
342        def later(val):
343            D = self.postForm(ctx, impl, "Inner", {'one': ['Not an integer'], 'two': ['22']})
344
345            def after(result):
346
347                self.assertEquals(impl.one, 1)
348                self.assertEquals(impl.two, 2)
349                self.assertEquals(impl.buckled, False)
350                self.assertEquals(impl.buried, False)
351
352                def evenlater(moreval):
353                    self.assertSubstring("is not an integer", moreval)
354                    # TODO: Get default values for property groups displaying properly.
355                    #self.assertSubstring('value="Not an integer"', moreval)
356                    DD = self.postForm(ctx, impl, "Inner", {'one': ['11'], 'two': ['22']})
357                    def afterafter(ign):
358                        self.assertEquals(impl.one, 11)
359                        self.assertEquals(impl.two, 22)
360                        self.assertEquals(impl.buckled, True)
361                        self.assertEquals(impl.buried, True)
362                    return DD.addCallback(afterafter)
363                return self.renderForms(impl, ctx).addCallback(evenlater)
364            return D.addCallback(after)
365        return self.renderForms(impl).addCallback(later)
366
367class TestRenderMethod(Base):
368
369    def testDefault(self):
370
371        class IFoo(formless.TypedInterface):
372            def foo(abc=formless.String()):
373                pass
374            foo = formless.autocallable(foo)
375
376        class Impl:
377            implements(IFoo)
378
379        def later(val):
380            self.assertSubstring('value="Foo"', val)
381            self.assertSubstring('name="abc"', val)
382        return self.renderForms(Impl(), bindingNames=['foo']).addCallback(later)
383
384
385    def testActionLabel(self):
386
387        class IFoo(formless.TypedInterface):
388            def foo(abc=formless.String()):
389                pass
390            foo = formless.autocallable(foo, action='FooFooFoo')
391
392        class Impl:
393            implements(IFoo)
394
395        def later(val):
396            self.assertSubstring('value="FooFooFoo"', val)
397            self.assertSubstring('name="abc"', val)
398        return self.renderForms(Impl(), bindingNames=['foo']).addCallback(later)
399
400    def testOneSigMultiCallables(self):
401
402        class IFoo(formless.TypedInterface):
403            def sig(abc=formless.String()):
404                pass
405            foo = formless.autocallable(sig)
406            bar = formless.autocallable(sig, action='FooFooFOo')
407
408        class Impl:
409            implements(IFoo)
410
411        def later1(val):
412            self.assertSubstring('value="Foo"', val)
413            def later2(val):
414                self.assertSubstring('value="FooFooFoo"', val)
415            return self.renderForms(Impl(), bindingNames=['bar']).addCallback(later2)
416        return self.renderForms(Impl(), bindingNames=['foo']).addCallback(later1)
417    testOneSigMultiCallables.todo = 'autocallable should not set attributes directly on the callable'
418
419
420class TestCustomTyped(Base):
421    def test_typedCoerceWithBinding(self):
422        class MyTyped(formless.Typed):
423            passed = False
424            wasBoundTo = None
425            def coerce(self, val, boundTo):
426                self.passed = True
427                self.wasBoundTo = boundTo
428                return "passed"
429
430        typedinst = MyTyped()
431
432        class IMyInterface(formless.TypedInterface):
433            def theFunc(test=typedinst):
434                pass
435            theFunc = formless.autocallable(theFunc)
436
437        class Implementation(object):
438            implements(IMyInterface)
439            called = False
440            def theFunc(self, test):
441                self.called = True
442
443        inst = Implementation()
444        ctx = self.setupContext()
445        D = self.postForm(ctx, inst, 'theFunc', {'test': ['a test value']})
446        def after(result):
447            self.assertEquals(typedinst.passed, True)
448            self.assertEquals(typedinst.wasBoundTo, inst)
449            self.assertEquals(inst.called, True)
450        return D.addCallback(after)
451
452
453class TestUneditableProperties(Base):
454    def test_uneditable(self):
455        class Uneditable(formless.TypedInterface):
456            aProp = formless.String(description="the description", immutable=True)
457
458        class Impl(object):
459            implements(Uneditable)
460
461            aProp = property(lambda self: "HELLO")
462
463        inst = Impl()
464
465        def later(val):
466            self.assertSubstring('HELLO', val)
467            self.failIfSubstring('type="text"', val)
468        return self.renderForms(inst).addCallback(later)
469
470
471class TestAfterValidation(Base):
472    """Test post-validation rendering"""
473
474    def test_property(self):
475        """Test that, when validation fails, the value just entered is redisplayed"""
476
477        class IThing(formless.TypedInterface):
478            foo = formless.Integer()
479
480        class Thing:
481            implements(IThing)
482            foo = 1
483
484        inst = Thing()
485        ctx = self.setupContext()
486        D = self.postForm(ctx, inst, 'foo', {'foo': ['abc']})
487        def after(result):
488            def later(val):
489                def morelater(noval):
490                    self.assertSubstring('value="abc"', val)
491                return self.renderForms(inst, ctx).addCallback(morelater)
492            return self.renderForms(inst)
493        return D.addCallback(after)
494
495
496class TestHandAndStatus(Base):
497    """Test that the method result is available as the hand, and that
498    a reasonable status message string is available"""
499    def test_hand(self):
500        """Test that the hand and status message are available before redirecting the post
501        """
502        returnResult = object()
503        class IMethod(formless.TypedInterface):
504            def foo(): pass
505            foo = formless.autocallable(foo)
506
507        class Method(object):
508            implements(IMethod)
509            def foo(self):
510                return returnResult
511
512        inst = Method()
513        ctx = self.setupContext()
514        D = self.postForm(ctx, inst, 'foo', {})
515        def after(result):
516            self.assertEquals(ctx.locate(inevow.IHand), returnResult)
517            self.assertEquals(ctx.locate(inevow.IStatusMessage), "'foo' success.")
518        return D.addCallback(after)
519
520    def test_handFactory(self):
521        """Test that the hand and status message are available after redirecting the post
522        """
523        returnResult = object()
524        status = 'horray'
525        def setupRequest(r):
526            r.args['_nevow_carryover_'] = ['abc']
527            from nevow import rend
528            c = components.Componentized()
529            c.setComponent(inevow.IHand, returnResult)
530            c.setComponent(inevow.IStatusMessage, status)
531            rend._CARRYOVER['abc'] = c
532            return r
533        ctx = self.setupContext(setupRequest=setupRequest)
534
535        self.assertEquals(ctx.locate(inevow.IHand), returnResult)
536        self.assertEquals(ctx.locate(inevow.IStatusMessage), status)
537
538
539class TestCharsetDetectionSupport(Base):
540
541    def test_property(self):
542
543        class ITest(formless.TypedInterface):
544            foo = formless.String()
545
546        class Impl:
547            implements(ITest)
548
549        impl = Impl()
550        ctx = self.setupContext()
551        def later(val):
552            self.assertIn('<input name="_charset_" type="hidden" />', val)
553            self.assertIn('accept-charset="utf-8"', val)
554        return self.renderForms(impl, ctx).addCallback(later)
555
556
557    def test_group(self):
558
559        class ITest(formless.TypedInterface):
560            class Group(formless.TypedInterface):
561                foo = formless.String()
562
563        class Impl:
564            implements(ITest)
565
566        impl = Impl()
567        ctx = self.setupContext()
568        def later(val):
569            self.assertIn('<input name="_charset_" type="hidden" />', val)
570            self.assertIn('accept-charset="utf-8"', val)
571        return self.renderForms(impl, ctx).addCallback(later)
572
573
574    def test_method(self):
575
576        class ITest(formless.TypedInterface):
577            def foo(foo = formless.String()):
578                pass
579            foo = formless.autocallable(foo)
580
581        class Impl:
582            implements(ITest)
583
584        impl = Impl()
585        ctx = self.setupContext()
586        def later(val):
587            self.assertIn('<input name="_charset_" type="hidden" />', val)
588            self.assertIn('accept-charset="utf-8"', val)
589        return self.renderForms(impl, ctx).addCallback(later)
590
591
592class TestUnicode(Base):
593
594    def test_property(self):
595
596        class IThing(formless.TypedInterface):
597            aString = formless.String(unicode=True)
598
599        class Impl(object):
600            implements(IThing)
601            aString = None
602
603        inst = Impl()
604        ctx = self.setupContext()
605        D = self.postForm(ctx, inst, 'aString', {'aString':['\xc2\xa3']})
606        return D.addCallback(lambda result: self.assertEquals(inst.aString, u'\xa3'))
607
608class TestChoice(Base):
609    """Test various behaviors of submitting values to a Choice Typed.
610    """
611
612    def test_reject_missing(self):
613        # Ensure that if a Choice is not specified, the form is not submitted.
614
615        self.called = []
616
617        class IFormyThing(formless.TypedInterface):
618            def choiceyFunc(arg = formless.Choice(["one", "two"], required=True)):
619                pass
620            choiceyFunc = formless.autocallable(choiceyFunc)
621
622        class Impl(object):
623            implements(IFormyThing)
624
625            def choiceyFunc(innerSelf, arg):
626                self.called.append(arg)
627
628        inst = Impl()
629        ctx = self.setupContext()
630        D = self.postForm(ctx, inst, 'choiceyFunc', {})
631        return D.addCallback(lambda result: self.assertEquals(self.called, []))
632
633
634class mg(Base):
635
636    def test_leakyForms(self):
637
638        class ITest(formless.TypedInterface):
639            """Test that a property value on one form does not 'leak' into
640            a property of the same name on another form.
641            """
642            foo = formless.String()
643
644            def meth(foo = formless.String()):
645                pass
646            meth = formless.autocallable(meth)
647
648        class Impl:
649            implements(ITest)
650            foo = 'fooFOOfoo'
651
652        impl = Impl()
653        ctx = self.setupContext()
654        def later(val):
655            self.assertEquals(val.count('fooFOOfoo'), 1)
656        return self.renderForms(impl, ctx)
657
658
659# What the *hell* is this?!?
660
661#DeferredTestCases = type(Base)(
662#    'DeferredTestCases',
663#    tuple([v for v in locals().values()
664#     if isinstance(v, type(Base)) and issubclass(v, Base)]),
665#    {'synchronousLocateConfigurable': True})
666
667