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